From bb06b567616d161736ee393cc3615f9bfc950c41 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 27 Jun 2024 23:58:40 +0200 Subject: [PATCH] metrics::moves: mimick passed range iterator category --- docs/Metrics.md | 4 +- .../cpp-sort/detail/fake_category_iterator.h | 249 ++++++++++++++++++ include/cpp-sort/metrics/moves.h | 13 +- tests/metrics/moves.cpp | 32 ++- 4 files changed, 295 insertions(+), 3 deletions(-) create mode 100644 include/cpp-sort/detail/fake_category_iterator.h diff --git a/docs/Metrics.md b/docs/Metrics.md index 36498423..21f611a5 100644 --- a/docs/Metrics.md +++ b/docs/Metrics.md @@ -52,7 +52,7 @@ Returns an instance of `utility::metric`. Computes the number of moves performed by the *adapted sorter*: it takes both the number of calls to the move constructor and to the move assignment operator of an object into account. A swap operation is considered equivalent to three moves. The tool currently works by creating a vector of a wrapper type which counts its moves, and moving the sorted contents back to the original collection, with the following implications: -* Sorters that behave differently depending on the iterator type always return the number of moves they perform when sorting random-access iterators. +* Said vector's iterators mimic the iterator category of the range passed to the *resulting sorter*: as such, behaviours that depend on the iterator _category_ are respected, but behaviours that depends on a specific iterator _type_ are not. * Sorters that call operations specific to some types might return a result that is not representative of how they actually perform: this is due to the wrapper not benefiting from the specializations. * Projection support is mandatory: `metrics::moves` passes a projection to the sorter in order to convert the wrapper type to its underlying type. @@ -68,6 +68,8 @@ Returns an instance of `utility::metric`. *New in version 1.16.0* +*Changed in version 1.17.0:* `metrics::moves` now mimicks the iterator category of the passed range passed to the *resulting sorter*, leading to more accurate results. + ### `projections` ```cpp diff --git a/include/cpp-sort/detail/fake_category_iterator.h b/include/cpp-sort/detail/fake_category_iterator.h new file mode 100644 index 00000000..a2987e99 --- /dev/null +++ b/include/cpp-sort/detail/fake_category_iterator.h @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2024 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_DETAIL_FAKE_CATEGORY_ITERATOR_H_ +#define CPPSORT_DETAIL_FAKE_CATEGORY_ITERATOR_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include "attributes.h" +#include "iterator_traits.h" +#include "type_traits.h" + +namespace cppsort +{ +namespace detail +{ + //////////////////////////////////////////////////////////// + // Iterator with fĂ ake iterator category: it wrap a given + // iterator and pretends to have a different iterator + // category as specified by users + + template + class fake_category_iterator + { + using real_category_tag = iterator_category_t; + static_assert( + std::is_base_of::value, + "The iterator tag of fake_iterator_category must be less " + "specific than that of the wrapped one" + ); + + public: + + //////////////////////////////////////////////////////////// + // Public types + + using iterator_category = IteratorTag; + using iterator_type = Iterator; + using value_type = value_type_t; + using difference_type = difference_type_t; + using pointer = pointer_t; + using reference = reference_t; + + //////////////////////////////////////////////////////////// + // Constructors + + fake_category_iterator() = default; + + explicit fake_category_iterator(Iterator it): + _it(std::move(it)) + {} + + //////////////////////////////////////////////////////////// + // Members access + + CPPSORT_ATTRIBUTE_NODISCARD + auto base() const + -> iterator_type + { + return _it; + } + + //////////////////////////////////////////////////////////// + // Element access + + CPPSORT_ATTRIBUTE_NODISCARD + auto operator*() const + -> decltype(*base()) + { + return *_it; + } + + CPPSORT_ATTRIBUTE_NODISCARD + auto operator->() const + -> pointer + { + return &(operator*()); + } + + //////////////////////////////////////////////////////////// + // Increment/decrement operators + + auto operator++() + -> fake_category_iterator& + { + ++_it; + return *this; + } + + auto operator++(int) + -> fake_category_iterator + { + auto res = *this; + ++_it; + return res; + } + + auto operator--() + -> fake_category_iterator& + { + --_it; + return *this; + } + + auto operator--(int) + -> fake_category_iterator + { + auto res = *this; + --_it; + return res; + } + + auto operator+=(difference_type increment) + -> fake_category_iterator& + { + _it += increment; + return *this; + } + + auto operator-=(difference_type increment) + -> fake_category_iterator& + { + _it -= increment; + return *this; + } + + //////////////////////////////////////////////////////////// + // Elements access operators + + CPPSORT_ATTRIBUTE_NODISCARD + auto operator[](difference_type pos) const + -> decltype(base()[pos]) + { + return _it[pos]; + } + + //////////////////////////////////////////////////////////// + // Comparison operators + + CPPSORT_ATTRIBUTE_NODISCARD + friend auto operator==(const fake_category_iterator& lhs, const fake_category_iterator& rhs) + -> bool + { + return lhs.base() == rhs.base(); + } + + CPPSORT_ATTRIBUTE_NODISCARD + friend auto operator!=(const fake_category_iterator& lhs, const fake_category_iterator& rhs) + -> bool + { + return lhs.base() != rhs.base(); + } + + //////////////////////////////////////////////////////////// + // Relational operators + + CPPSORT_ATTRIBUTE_NODISCARD + friend auto operator<(const fake_category_iterator& lhs, const fake_category_iterator& rhs) + -> bool + { + return lhs.base() < rhs.base(); + } + + CPPSORT_ATTRIBUTE_NODISCARD + friend auto operator<=(const fake_category_iterator& lhs, const fake_category_iterator& rhs) + -> bool + { + return lhs.base() <= rhs.base(); + } + + CPPSORT_ATTRIBUTE_NODISCARD + friend auto operator>(const fake_category_iterator& lhs, const fake_category_iterator& rhs) + -> bool + { + return lhs.base() > rhs.base(); + } + + CPPSORT_ATTRIBUTE_NODISCARD + friend auto operator>=(const fake_category_iterator& lhs, const fake_category_iterator& rhs) + -> bool + { + return lhs.base() >= rhs.base(); + } + + //////////////////////////////////////////////////////////// + // Arithmetic operators + + CPPSORT_ATTRIBUTE_NODISCARD + friend auto operator+(fake_category_iterator it, difference_type size) + -> fake_category_iterator + { + it += size; + return it; + } + + CPPSORT_ATTRIBUTE_NODISCARD + friend auto operator+(difference_type size, fake_category_iterator it) + -> fake_category_iterator + { + it += size; + return it; + } + + CPPSORT_ATTRIBUTE_NODISCARD + friend auto operator-(fake_category_iterator it, difference_type size) + -> fake_category_iterator + { + it -= size; + return it; + } + + CPPSORT_ATTRIBUTE_NODISCARD + friend auto operator-(const fake_category_iterator& lhs, const fake_category_iterator& rhs) + -> difference_type + { + return lhs.base() - rhs.base(); + } + + //////////////////////////////////////////////////////////// + // iter_move/iter_swap + + friend auto iter_swap(fake_category_iterator lhs, fake_category_iterator rhs) + -> void + { + using utility::iter_swap; + iter_swap(lhs.base(), rhs.base()); + } + + CPPSORT_ATTRIBUTE_NODISCARD + friend auto iter_move(fake_category_iterator it) + -> decltype(auto) + { + using utility::iter_move; + return iter_move(it.base()); + } + + private: + + Iterator _it; + }; +}} + +#endif // CPPSORT_DETAIL_FAKE_CATEGORY_ITERATOR_H_ diff --git a/include/cpp-sort/metrics/moves.h b/include/cpp-sort/metrics/moves.h index 37977270..8c47501a 100644 --- a/include/cpp-sort/metrics/moves.h +++ b/include/cpp-sort/metrics/moves.h @@ -18,6 +18,7 @@ #include #include #include "../detail/checkers.h" +#include "../detail/fake_category_iterator.h" #include "../detail/immovable_vector.h" #include "../detail/iterator_traits.h" #include "../detail/type_traits.h" @@ -75,6 +76,14 @@ namespace metrics cppsort::detail::rvalue_type_t, CountType >; + // Use a special iterator that wraps wrapper_t* and pretends + // it is of a different iterator category to better force the + // sorter to use strategies matching the category of the + // passed iterator type + using iterator_t = cppsort::detail::fake_category_iterator< + wrapper_t*, + cppsort::detail::iterator_category_t + >; // Use an immovable_vector to ensure that no move is performed // hile filling the vector @@ -85,10 +94,12 @@ namespace metrics } std::forward(sorter)( - vec.begin(), vec.end(), std::move(compare), + iterator_t(vec.begin()), iterator_t(vec.end()), std::move(compare), utility::as_projection(&wrapper_t::value) | std::move(projection) ); + // Move back the wrapped value only, not the wrapper itself, + // this ensures that those moves are not counted auto vec_it = vec.begin(); for (auto it = first; it != last; ++it, ++vec_it) { *it = std::move(vec_it->value); diff --git a/tests/metrics/moves.cpp b/tests/metrics/moves.cpp index 44971266..41822535 100644 --- a/tests/metrics/moves.cpp +++ b/tests/metrics/moves.cpp @@ -1,15 +1,18 @@ /* - * Copyright (c) 2023 Morwenn + * Copyright (c) 2023-2024 Morwenn * SPDX-License-Identifier: MIT */ #include #include #include #include +#include #include #include +#include #include #include +#include #include #include #include @@ -74,3 +77,30 @@ TEST_CASE( "metrics::moves with span", std::less<>{}, &wrapper::value) ); } } + +TEST_CASE( "metrics::moves over iterator-category-sensitive sorters", + "[metrics][hybriad_adapter][heap_sorter][insertion_sorter]" ) +{ + cppsort::metrics::moves< + cppsort::hybrid_adapter< + cppsort::insertion_sorter, + cppsort::heap_sorter + > + > sorter; + auto distribution = dist::descending_plateau{}; + + std::vector vec; + distribution(std::back_inserter(vec), 65); + std::list li(vec.begin(), vec.end()); + + { + auto res = sorter(vec); + CHECK( res == 463 ); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); + } + { + auto res = sorter(li); + CHECK( res == 1977 ); + CHECK( std::is_sorted(li.begin(), li.end()) ); + } +}