From 138fb5ed40a6365b6efc5505687db89dc6ba6a35 Mon Sep 17 00:00:00 2001 From: 5cript Date: Tue, 25 Aug 2026 19:11:56 +0200 Subject: [PATCH 1/3] Fixed overlap_find_next_in_subtree due to missing third argument. --- include/interval-tree/interval_tree.hpp | 29 +++++++++++++++++-------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/include/interval-tree/interval_tree.hpp b/include/interval-tree/interval_tree.hpp index 28860bd..8509acf 100644 --- a/include/interval-tree/interval_tree.hpp +++ b/include/interval-tree/interval_tree.hpp @@ -707,6 +707,10 @@ namespace lib_interval_tree const_interval_tree_iterator& operator=(const_interval_tree_iterator const&) = default; const_interval_tree_iterator& operator=(const_interval_tree_iterator&&) noexcept = default; + explicit const_interval_tree_iterator(interval_tree_iterator const& other) + : iterator_base{other.node_, other.owner_} + {} + template friend void increment(T& iter); template @@ -1357,11 +1361,11 @@ namespace lib_interval_tree } template const_iterator - find_next_in_subtree(iterator from, interval_type const& ival, CompareFunctionT const& compare) const + find_next_in_subtree(const_iterator from, interval_type const& ival, CompareFunctionT const& compare) const { if (root_ == nullptr) return end(); - return iterator{find_i_ex(from.node_, ival, compare), this}; + return const_iterator{find_i_ex(from.node_, ival, compare), this}; } /** @@ -1377,7 +1381,7 @@ namespace lib_interval_tree return lhs == rhs; }); } - const_iterator find_next_in_subtree(iterator from, interval_type const& ival) const + const_iterator find_next_in_subtree(const_iterator from, interval_type const& ival) const { return find_next_in_subtree(from, ival, [](auto const& lhs, auto const& rhs) { return lhs == rhs; @@ -1447,14 +1451,20 @@ namespace lib_interval_tree { if (root_ == nullptr) return end(); - return iterator{overlap_find_i_ex(from.node_, ival, exclusive), this}; + if (exclusive) + return iterator{overlap_find_i_ex(from.node_, ival), this}; + else + return iterator{overlap_find_i_ex(from.node_, ival), this}; } const_iterator overlap_find_next_in_subtree(const_iterator from, interval_type const& ival, bool exclusive = false) const { if (root_ == nullptr) return end(); - return const_iterator{overlap_find_i_ex(from.node_, ival, exclusive), this}; + if (exclusive) + return const_iterator{overlap_find_i_ex(from.node_, ival), this}; + else + return const_iterator{overlap_find_i_ex(from.node_, ival), this}; } /** @@ -1793,9 +1803,10 @@ namespace lib_interval_tree return find_i_ex(ptr, ival, compare); } - // excludes ptr + // excludes ptr, which is therefore never dereferenced for anything but its child pointers. template - node_type* find_i_ex(node_type* ptr, interval_type const& ival, ComparatorFunctionT const& compare) const + node_type* + find_i_ex(node_type const* ptr, interval_type const& ival, ComparatorFunctionT const& compare) const { if (ptr->left_ && ival.high() <= ptr->left_->max()) { @@ -1877,9 +1888,9 @@ namespace lib_interval_tree return true; } - // excludes ptr + // excludes ptr, which is therefore never dereferenced for anything but its child pointers. template - node_type* overlap_find_i_ex(node_type* ptr, interval_type const& ival) const + node_type* overlap_find_i_ex(node_type const* ptr, interval_type const& ival) const { if (ptr->left_ && ptr->left_->max() >= ival.low()) { From b8c30951d8fbe29248b0e5852afeb5f495883362 Mon Sep 17 00:00:00 2001 From: 5cript Date: Tue, 25 Aug 2026 19:13:15 +0200 Subject: [PATCH 2/3] Added test for find_next_in_subtree bug. --- tests/find_tests.hpp | 41 +++++++++++++++++++ tests/overlap_find_tests.hpp | 76 ++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+) diff --git a/tests/find_tests.hpp b/tests/find_tests.hpp index c1f5429..d6915b1 100644 --- a/tests/find_tests.hpp +++ b/tests/find_tests.hpp @@ -165,6 +165,47 @@ TEST_F(FindTests, CanFindAllOnConstTree) EXPECT_EQ(intervals[0], targetInterval); } +TEST_F(FindTests, WillReturnEndForNextInSubtreeIfTreeIsEmpty) +{ + EXPECT_EQ(tree.find_next_in_subtree(tree.root(), {2, 7}), std::end(tree)); +} + +TEST_F(FindTests, WillFindNextExactMatchInSubtree) +{ + const auto targetInterval = decltype(tree)::interval_type{0, 5}; + tree.insert(targetInterval); + tree.insert({10, 15}); + tree.insert(targetInterval); + tree.insert({30, 35}); + + // The node started from is excluded, so this must find the duplicate and not the root itself. + const auto found = tree.find_next_in_subtree(tree.root(), targetInterval); + ASSERT_NE(found, std::end(tree)); + EXPECT_EQ(*found, targetInterval); + EXPECT_NE(found, tree.root()); + + EXPECT_EQ(tree.find_next_in_subtree(tree.root(), {99, 100}), std::end(tree)); +} + +TEST_F(FindTests, WillFindNextExactMatchInSubtreeOnConstTree) +{ + const auto targetInterval = decltype(tree)::interval_type{0, 5}; + tree.insert(targetInterval); + tree.insert({10, 15}); + tree.insert(targetInterval); + tree.insert({30, 35}); + + [&targetInterval](auto const& tree) { + const auto found = tree.find_next_in_subtree(tree.root(), targetInterval); + static_assert( + std::is_same::const_iterator const>::value, + "the const overload must yield a const_iterator" + ); + ASSERT_NE(found, std::end(tree)); + EXPECT_EQ(*found, targetInterval); + }(tree); +} + TEST_F(FindTests, FuzzyFindAllInTree) { std::mt19937 gen{0}; diff --git a/tests/overlap_find_tests.hpp b/tests/overlap_find_tests.hpp index ed2671b..33efeea 100644 --- a/tests/overlap_find_tests.hpp +++ b/tests/overlap_find_tests.hpp @@ -182,6 +182,82 @@ TEST_F(OverlapFindTests, CanOverlapFindAllOnConstTree) EXPECT_EQ(intervals[0], targetInterval); } +TEST_F(OverlapFindTests, WillReturnEndForNextInSubtreeIfTreeIsEmpty) +{ + EXPECT_EQ(tree.overlap_find_next_in_subtree(tree.root(), {2, 7}), std::end(tree)); +} + +TEST_F(OverlapFindTests, WillFindNextOverlapInSubtree) +{ + tree.insert({0, 5}); + tree.insert({10, 15}); + tree.insert({20, 25}); + tree.insert({30, 35}); + + const auto found = tree.overlap_find_next_in_subtree(tree.root(), {21, 22}); + ASSERT_NE(found, std::end(tree)); + EXPECT_EQ(*found, (decltype(tree)::interval_type{20, 25})); +} + +TEST_F(OverlapFindTests, WillNotFindNextOverlapInSubtreeWithTheStartNodeItself) +{ + tree.insert({0, 5}); + tree.insert({10, 15}); + tree.insert({20, 25}); + tree.insert({30, 35}); + + // The node started from is excluded and all intervals are disjoint, so nothing else can overlap it. + const auto rootInterval = *tree.root(); + EXPECT_EQ(tree.overlap_find_next_in_subtree(tree.root(), rootInterval), std::end(tree)); +} + +TEST_F(OverlapFindTests, WillObeyExclusiveWhenFindingNextOverlapInSubtree) +{ + tree.insert({0, 5}); + tree.insert({10, 15}); + tree.insert({20, 25}); + tree.insert({30, 35}); + + // {25, 27} touches {20, 25} on the border only. + const auto inclusive = tree.overlap_find_next_in_subtree(tree.root(), {25, 27}, false); + ASSERT_NE(inclusive, std::end(tree)); + EXPECT_EQ(*inclusive, (decltype(tree)::interval_type{20, 25})); + + EXPECT_EQ(tree.overlap_find_next_in_subtree(tree.root(), {25, 27}, true), std::end(tree)); +} + +TEST_F(OverlapFindTests, WillFindNextOverlapInSubtreeOnConstTree) +{ + tree.insert({0, 5}); + tree.insert({10, 15}); + tree.insert({20, 25}); + tree.insert({30, 35}); + + const auto expected = decltype(tree)::interval_type{20, 25}; + [&expected](auto const& tree) { + const auto found = tree.overlap_find_next_in_subtree(tree.root(), {21, 22}); + ASSERT_NE(found, std::end(tree)); + EXPECT_EQ(*found, expected); + }(tree); +} + +TEST_F(OverlapFindTests, CanConvertIteratorToConstIteratorForNextOverlapInSubtree) +{ + tree.insert({0, 5}); + tree.insert({10, 15}); + tree.insert({20, 25}); + tree.insert({30, 35}); + + // A mutable iterator has to be converted explicitly to reach the const overload. + const auto from = decltype(tree)::const_iterator{tree.root()}; + const auto expected = decltype(tree)::interval_type{20, 25}; + [&from, &expected](auto const& tree) { + const auto found = tree.overlap_find_next_in_subtree(from, {21, 22}); + ASSERT_NE(found, std::end(tree)); + EXPECT_EQ(*found, expected); + }(tree); +} + TEST_F(OverlapFindTests, FuzzyOverlapFindAllInTree) { std::mt19937 gen{0}; From 07bf9e89ce2801341ad268a9be2690ea53633c53 Mon Sep 17 00:00:00 2001 From: 5cript Date: Tue, 25 Aug 2026 19:16:13 +0200 Subject: [PATCH 3/3] Readded non const iterator overloads. --- include/interval-tree/interval_tree.hpp | 18 ++++++++++++++++++ tests/find_tests.hpp | 24 ++++++++++++++++++++++++ tests/overlap_find_tests.hpp | 12 ++++++++---- 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/include/interval-tree/interval_tree.hpp b/include/interval-tree/interval_tree.hpp index 8509acf..40b033a 100644 --- a/include/interval-tree/interval_tree.hpp +++ b/include/interval-tree/interval_tree.hpp @@ -1367,6 +1367,13 @@ namespace lib_interval_tree return end(); return const_iterator{find_i_ex(from.node_, ival, compare), this}; } + // Convenience, so that a mutable iterator does not have to be converted by hand. + template + const_iterator + find_next_in_subtree(iterator from, interval_type const& ival, CompareFunctionT const& compare) const + { + return find_next_in_subtree(const_iterator{from}, ival, compare); + } /** * Finds the next exact match EXCLUDING from. @@ -1387,6 +1394,11 @@ namespace lib_interval_tree return lhs == rhs; }); } + // Convenience, so that a mutable iterator does not have to be converted by hand. + const_iterator find_next_in_subtree(iterator from, interval_type const& ival) const + { + return find_next_in_subtree(const_iterator{from}, ival); + } /** * Finds the first interval that overlaps with ival. @@ -1466,6 +1478,12 @@ namespace lib_interval_tree else return const_iterator{overlap_find_i_ex(from.node_, ival), this}; } + // Convenience, so that a mutable iterator does not have to be converted by hand. + const_iterator + overlap_find_next_in_subtree(iterator from, interval_type const& ival, bool exclusive = false) const + { + return overlap_find_next_in_subtree(const_iterator{from}, ival, exclusive); + } /** * Deoverlaps the tree but returns it as a copy. diff --git a/tests/find_tests.hpp b/tests/find_tests.hpp index d6915b1..f31d7d0 100644 --- a/tests/find_tests.hpp +++ b/tests/find_tests.hpp @@ -206,6 +206,30 @@ TEST_F(FindTests, WillFindNextExactMatchInSubtreeOnConstTree) }(tree); } +TEST_F(FindTests, CanFindNextExactMatchInSubtreeOnConstTreeFromMutableIterator) +{ + const auto targetInterval = decltype(tree)::interval_type{0, 5}; + tree.insert(targetInterval); + tree.insert({10, 15}); + tree.insert(targetInterval); + tree.insert({30, 35}); + + // A mutable iterator is accepted by the const overloads without converting it by hand. + const auto from = tree.root(); + const auto compare = [](auto const& lhs, auto const& rhs) { + return lhs == rhs; + }; + [&from, &targetInterval, &compare](auto const& tree) { + const auto found = tree.find_next_in_subtree(from, targetInterval); + ASSERT_NE(found, std::end(tree)); + EXPECT_EQ(*found, targetInterval); + + const auto foundWithCompare = tree.find_next_in_subtree(from, targetInterval, compare); + ASSERT_NE(foundWithCompare, std::end(tree)); + EXPECT_EQ(*foundWithCompare, targetInterval); + }(tree); +} + TEST_F(FindTests, FuzzyFindAllInTree) { std::mt19937 gen{0}; diff --git a/tests/overlap_find_tests.hpp b/tests/overlap_find_tests.hpp index 33efeea..c7632ea 100644 --- a/tests/overlap_find_tests.hpp +++ b/tests/overlap_find_tests.hpp @@ -248,13 +248,17 @@ TEST_F(OverlapFindTests, CanConvertIteratorToConstIteratorForNextOverlapInSubtre tree.insert({20, 25}); tree.insert({30, 35}); - // A mutable iterator has to be converted explicitly to reach the const overload. - const auto from = decltype(tree)::const_iterator{tree.root()}; + // The conversion itself is explicit, but the const overloads also accept a mutable iterator directly. + const auto converted = decltype(tree)::const_iterator{tree.root()}; + const auto mutableIterator = tree.root(); const auto expected = decltype(tree)::interval_type{20, 25}; - [&from, &expected](auto const& tree) { - const auto found = tree.overlap_find_next_in_subtree(from, {21, 22}); + [&converted, &mutableIterator, &expected](auto const& tree) { + const auto found = tree.overlap_find_next_in_subtree(converted, {21, 22}); ASSERT_NE(found, std::end(tree)); EXPECT_EQ(*found, expected); + + const auto fromMutable = tree.overlap_find_next_in_subtree(mutableIterator, {21, 22}); + EXPECT_EQ(fromMutable, found); }(tree); }