From a8ebe338f80ee25fcc79d53cdbc8140bf208fae1 Mon Sep 17 00:00:00 2001 From: Roland Reichwein Date: Fri, 6 Mar 2026 11:11:46 +0100 Subject: [PATCH] Fix etl::rotate (#1327) Per the C++ standard, std::rotate returns first + (last - middle): * When first == middle, return last * When middle == last, return first --- include/etl/algorithm.h | 28 +++++++++-- test/iterators_for_unit_tests.h | 6 +++ test/test_algorithm.cpp | 87 +++++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+), 4 deletions(-) diff --git a/include/etl/algorithm.h b/include/etl/algorithm.h index 74fbb8ed..8ee795a8 100644 --- a/include/etl/algorithm.h +++ b/include/etl/algorithm.h @@ -1193,7 +1193,12 @@ namespace etl typename etl::enable_if::value, TIterator>::type rotate_general(TIterator first, TIterator middle, TIterator last) { - if (first == middle || middle == last) + if (first == middle) + { + return last; + } + + if (middle == last) { return first; } @@ -1242,7 +1247,12 @@ namespace etl typename etl::enable_if::value, TIterator>::type rotate_general(TIterator first, TIterator middle, TIterator last) { - if (first == middle || middle == last) + if (first == middle) + { + return last; + } + + if (middle == last) { return first; } @@ -1292,7 +1302,12 @@ namespace etl typename etl::enable_if::value, TIterator>::type rotate_general(TIterator first, TIterator middle, TIterator last) { - if (first == middle || middle == last) + if (first == middle) + { + return last; + } + + if (middle == last) { return first; } @@ -1314,7 +1329,12 @@ namespace etl typename etl::enable_if::value, TIterator>::type rotate_general(TIterator first, TIterator middle, TIterator last) { - if (first == middle || middle == last) + if (first == middle) + { + return last; + } + + if (middle == last) { return first; } diff --git a/test/iterators_for_unit_tests.h b/test/iterators_for_unit_tests.h index 16085f87..82d28c6c 100644 --- a/test/iterators_for_unit_tests.h +++ b/test/iterators_for_unit_tests.h @@ -113,6 +113,12 @@ struct non_random_iterator : public etl::iterator +bool operator ==(const non_random_iterator& lhs, const non_random_iterator& rhs) +{ + return lhs.ptr == rhs.ptr; +} + template bool operator !=(const non_random_iterator& lhs, const non_random_iterator& rhs) { diff --git a/test/test_algorithm.cpp b/test/test_algorithm.cpp index abdab9d5..4dd01e9f 100644 --- a/test/test_algorithm.cpp +++ b/test/test_algorithm.cpp @@ -1236,6 +1236,93 @@ namespace } } + //************************************************************************* + TEST(rotate_return_value) + { + // Verify that etl::rotate returns the same iterator as std::rotate + // in all cases, including the degenerate first==middle and middle==last cases. + std::vector initial_data = { 1, 2, 3, 4, 5 }; + + for (size_t i = 0UL; i <= initial_data.size(); ++i) + { + std::vector data1(initial_data); + std::vector data2(initial_data); + + auto std_result = std::rotate(data1.data(), data1.data() + i, data1.data() + data1.size()); + auto etl_result = etl::rotate(data2.data(), data2.data() + i, data2.data() + data2.size()); + + // Check that the return value offset matches + ptrdiff_t std_offset = std_result - data1.data(); + ptrdiff_t etl_offset = etl_result - data2.data(); + CHECK_EQUAL(std_offset, etl_offset); + } + + // Explicitly test first == middle (empty left half): should return last + { + std::vector data = { 1, 2, 3 }; + auto result = etl::rotate(data.data(), data.data(), data.data() + data.size()); + CHECK(result == data.data() + data.size()); + } + + // Explicitly test middle == last (empty right half): should return first + { + std::vector data = { 1, 2, 3 }; + auto result = etl::rotate(data.data(), data.data() + data.size(), data.data() + data.size()); + CHECK(result == data.data()); + } + } + + //************************************************************************* + TEST(rotate_return_value_non_random_iterator) + { + // Verify that etl::rotate returns the correct iterator when called with + // non-random (bidirectional) iterators, exercising rotate_general for + // bidirectional iterators rather than the random-access overload. + std::vector initial_data = { 1, 2, 3, 4, 5 }; + + for (size_t i = 0UL; i <= initial_data.size(); ++i) + { + std::vector data1(initial_data); + std::vector data2(initial_data); + + auto std_result = std::rotate(data1.data(), data1.data() + i, data1.data() + data1.size()); + + non_random_iterator nr_first(data2.data()); + non_random_iterator nr_middle(data2.data() + i); + non_random_iterator nr_last(data2.data() + data2.size()); + auto etl_result = etl::rotate(nr_first, nr_middle, nr_last); + + // Check that the data was rotated correctly + bool isEqual = std::equal(std::begin(data1), std::end(data1), std::begin(data2)); + CHECK(isEqual); + + // Check that the return value offset matches + ptrdiff_t std_offset = std_result - data1.data(); + ptrdiff_t etl_offset = etl_result.ptr - data2.data(); + CHECK_EQUAL(std_offset, etl_offset); + } + + // Explicitly test first == middle (empty left half): should return last + { + std::vector data = { 1, 2, 3 }; + non_random_iterator nr_first(data.data()); + non_random_iterator nr_middle(data.data()); + non_random_iterator nr_last(data.data() + data.size()); + auto result = etl::rotate(nr_first, nr_middle, nr_last); + CHECK(result.ptr == data.data() + data.size()); + } + + // Explicitly test middle == last (empty right half): should return first + { + std::vector data = { 1, 2, 3 }; + non_random_iterator nr_first(data.data()); + non_random_iterator nr_middle(data.data() + data.size()); + non_random_iterator nr_last(data.data() + data.size()); + auto result = etl::rotate(nr_first, nr_middle, nr_last); + CHECK(result.ptr == data.data()); + } + } + //************************************************************************* TEST(any_of) {