diff --git a/CMakeLists.txt b/CMakeLists.txt index a014b15b..4a78f7c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ cmake_minimum_required(VERSION 3.8.0) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) -project(cpp-sort VERSION 1.12.0 LANGUAGES CXX) +project(cpp-sort VERSION 1.12.1 LANGUAGES CXX) include(CMakePackageConfigHelpers) include(GNUInstallDirs) diff --git a/README.md b/README.md index 834a6574..2b81af77 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -[![Latest Release](https://img.shields.io/badge/release-1.12.0-blue.svg)](https://github.com/Morwenn/cpp-sort/releases/tag/1.12.0) -[![Conan Package](https://img.shields.io/badge/conan-cpp--sort%2F1.12.0-blue.svg)](https://conan.io/center/cpp-sort?version=1.12.0) +[![Latest Release](https://img.shields.io/badge/release-1.12.1-blue.svg)](https://github.com/Morwenn/cpp-sort/releases/tag/1.12.1) +[![Conan Package](https://img.shields.io/badge/conan-cpp--sort%2F1.12.1-blue.svg)](https://conan.io/center/cpp-sort?version=1.12.1) [![Code Coverage](https://codecov.io/gh/Morwenn/cpp-sort/branch/develop/graph/badge.svg)](https://codecov.io/gh/Morwenn/cpp-sort) > *It would be nice if only one or two of the sorting methods would dominate all of the others, diff --git a/conanfile.py b/conanfile.py index 0c0cc3b4..f5d54780 100644 --- a/conanfile.py +++ b/conanfile.py @@ -10,7 +10,7 @@ class CppSortConan(ConanFile): name = "cpp-sort" - version = "1.12.0" + version = "1.12.1" description = "Additional sorting algorithms & related tools" topics = "conan", "cpp-sort", "sorting", "algorithms" url = "https://github.com/Morwenn/cpp-sort" diff --git a/docs/Home.md b/docs/Home.md index c9239310..5c692680 100644 --- a/docs/Home.md +++ b/docs/Home.md @@ -1,4 +1,4 @@ -Welcome to the **cpp-sort 1.12.0** documentation! +Welcome to the **cpp-sort 1.12.1** documentation! You probably read the introduction in the README, so I won't repeat it here. This wiki contains documentation about the library: basic documentation about the many sorting tools and how to use them, documentation about the additional utilities provided by the library and even some detailed tutorials if you ever want to write your own sorters or sorter adapters. This main page explains a few general things that didn't quite fit in other parts of the documentation. diff --git a/docs/Miscellaneous-utilities.md b/docs/Miscellaneous-utilities.md index f975f2c9..d1b8781f 100644 --- a/docs/Miscellaneous-utilities.md +++ b/docs/Miscellaneous-utilities.md @@ -247,6 +247,8 @@ using make_index_range = make_integer_range; `size` is a function that can be used to get the size of an iterable. It is equivalent to the C++17 function [`std::size`][std-size] but has an additional tweak so that, if the iterable is not a fixed-size C array and doesn't have a `size` method, it calls `std::distance(std::begin(iter), std::end(iter))` on the iterable. Therefore, this function can also be used for `std::forward_list` as well as some implementations of ranges. +*Changed in version 1.12.1:* `utility::size()` now also works for collections that only provide non-`const` `begin()` and `end()`. + ### Sorting network tools ```cpp diff --git a/docs/Sorters.md b/docs/Sorters.md index c844965f..e78f90de 100644 --- a/docs/Sorters.md +++ b/docs/Sorters.md @@ -309,7 +309,7 @@ This sorter also has the following dedicated algorithms when used together with None of the container-aware algorithms invalidates iterators. -### `slab_sort` +### `slab_sorter` ```cpp #include @@ -319,12 +319,14 @@ Implements a variant of slabsort, a rather slow but highly adaptive algorithm de | Best | Average | Worst | Memory | Stable | Iterators | | ----------- | ----------- | ----------- | ----------- | ----------- | ------------- | -| n | n log n | n log n | n | No | Random-access | +| n | n log n | n log n | n | No | Bidirectional | This algorithm actually uses a rather big amount of memory but scales better than other O(n log n) algorithms of the library described as "slow" when the collections get bigger. *New in version 1.10.0* +*Changed in version 1.12.1:* `slab_sorter` now works with bidirectional iterators. + ### `smooth_sorter` ```cpp diff --git a/docs/Tooling.md b/docs/Tooling.md index 05c24c33..a4dc51cc 100644 --- a/docs/Tooling.md +++ b/docs/Tooling.md @@ -51,10 +51,10 @@ The same options exist without the `CPPSORT_` prefix exist, but are deprecated. conan search cpp-sort --remote=conan-center ``` -And then install any version to your local cache as follows (here with version 1.12.0): +And then install any version to your local cache as follows (here with version 1.12.1): ```sh -conan install cpp-sort/1.12.0 +conan install cpp-sort/1.12.1 ``` The packages downloaded from conan-center are minimal and only contain the files required to use **cpp-sort** as a library: the headers, CMake files and licensing information. If you need anything else you have to build your own package with the `conanfile.py` available in this repository. diff --git a/docs/Writing-a-bubble_sorter.md b/docs/Writing-a-bubble_sorter.md index 4df61e4e..8847f2ca 100644 --- a/docs/Writing-a-bubble_sorter.md +++ b/docs/Writing-a-bubble_sorter.md @@ -1,8 +1,8 @@ -If you have read the general tutorial about [[writing sorters|Writing a sorter]], you might be interested in a full concrete example. In this tutorial, we will see how to implement a simple [bubble sort](https://en.wikipedia.org/wiki/Bubble_sort) and how to write a `bubble_sorter` to wrap it. Step by step. +If you have read the general tutorial about [[writing sorters|Writing a sorter]], you might be interested in a full concrete example. In this tutorial, we will see how to implement a simple [bubble sort][bubble-sort] and how to write a `bubble_sorter` to wrap it. Step by step. ## The bubble sort algorithm -The bubble sort is one of the simplest sorting algorithms to implement: it repeatedly goes through a collection, comparing adjacent elements and switching them if they are not in order, until the collection is sorted. There are some very specific cases where it might be the ideal algorithm, but it is useless most of the time. Anyway, here is a basic implementation taking a pair of iterators like many standard library algorithms: +Bubble sort is one of the simplest sorting algorithms to implement: it repeatedly goes through a collection, comparing adjacent elements and switching them if they are not in order, until the collection is sorted. There are some very specific cases where it might be the ideal algorithm, but most of the time you're better off using another algorithm. Here is a basic implementation taking a pair of iterators like many standard library algorithms: ```cpp template @@ -10,7 +10,7 @@ auto bubble_sort(RandomAccessIterator first, RandomAccessIterator last) -> void { while (first != last--) { - for (auto it = first ; it != last ; ++it) { + for (auto it = first; it != last; ++it) { if (*(it + 1) < *it) { std::iter_swap(it, it + 1); } @@ -19,7 +19,7 @@ auto bubble_sort(RandomAccessIterator first, RandomAccessIterator last) } ``` -This version works with random-access iterators only. That said, making it work with bidirectional iterators too is only a matter of changing the `it + 1` into an `std::next(it)`: +This version only works with random-access iterators only. That said, lowering the accepting iterator category to bidirectional iterators is merely a matter of changing `it + 1` into a more generic `std::next(it)`: ```cpp template @@ -27,7 +27,7 @@ auto bubble_sort(BidirectionalIterator first, BidirectionalIterator last) -> void { while (first != last--) { - for (auto it = first ; it != last ; ++it) { + for (auto it = first; it != last; ++it) { auto next = std::next(it); if (*next < *it) { std::iter_swap(it, next); @@ -41,7 +41,7 @@ Some versions of `bubble_sort` track whether swaps were actually performed durin ## A simple `bubble_sorter` -Now that we have a working `bubble_sort` algorithm, we will wrap it into a sorter so that it can benefit from the many tools available in **cpp-sort** when needed. Here is a very basic `bubble_sorter` implementation: +Now that we have a working `bubble_sort` algorithm, we will wrap it into a sorter so that it can benefit from the many tools available in **cpp-sort**. Here is a very basic `bubble_sorter` implementation: ```cpp struct bubble_sorter @@ -55,7 +55,7 @@ struct bubble_sorter }; ``` -Unfortunately, **cpp-sort** requires sorters to implement range-based algorithms too in order to satisfy the `Sorter` concept. Implementing by hand the whole set of features is boring and error-prone. Fortunately, **cpp-sort** provides the utility class template [`sorter_facade`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-facade) to automagically generate the missing features when an iterator-based `operator()` exists: +Unfortunately, **cpp-sort** requires sorters to implement range-based algorithms too in order to satisfy the *Sorter* requirements, and implementing the whole set of features by hand is boring and error-prone. Fortunately, **cpp-sort** provides [`sorter_facade`][sorter-facade], a class template to automagically generate the missing features when an iterator-based `operator()` is provided: ```cpp struct bubble_sorter_impl @@ -71,7 +71,11 @@ struct bubble_sorter_impl using bubble_sorter = cppsort::sorter_facade; ``` -Now our `bubble_sorter` satisfies the library's requirements and implements all the additional features without too much additional work. However, we might also want it to play nice with [`hybrid_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#hybrid_adapter), the main building block which allows to aggregate different sorters together. In order to do so, we need to explicitly document the iterator category the sorter is designed to work with. We can do so by giving it an `iterator_category` type aliasing one of the standard iterator tags: +Now our `bubble_sorter` satisfies the library's requirements and implements all the additional features without too much additional work. We could stop there, but there is still some work ahead if we want it to play nice with all the features the library has to offer... + +## Sorter traits + +For example let's take [`hybrid_adapter`][hybrid-adapter], a [[*sorter adapter*|Sorter adapters]] which allows to aggregate different sorters together: it needs to know the iterator category of the sorters it aggregates. In order to provide that information, we need to explicitly document the iterator category our sorter is designed to work with by giving it an `iterator_category` type aliasing one of the standard iterator tags: ```cpp struct bubble_sorter_impl @@ -89,23 +93,22 @@ struct bubble_sorter_impl }; ``` -Documenting the stability of a sorter through `is_always_stable` is required if we want it to work with [`stable_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#stable_adapter); this adapter transforms a sorter to make a stable sorter, but explicitly specifying that our `bubble_sorter` is stable will allow `stable_adapter` to skip the transformation and use the sorter as is. Even though it doesn't always matter, accessing these properties should be done via the class [`sorter_traits`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-traits); some sorters don't embed these properties and specialize `sorter_traits` instead to provide them. +As you might have noticed, the snippet above also provides `is_always_stable`, a trait documenting the stability of the sorter which is notably used by another component: [`make_stable`][stable-adapter]. This adapter transforms any sorter into a stable sorter, but explicitly specifying that our `bubble_sorter` is stable always will allow `make_stable` to skip the transformation and use the sorter directly. + +Those traits can be provided directly in the sorter for simplicity, but accessing these properties should be done via [`sorter_traits`][sorter-traits] and related facilities for a variety of reasons: some sorters notably don't embed these properties and specialize `sorter_traits` instead to provide them. As a result, `sorter_traits` should always be considered the main source of truth when querying for sorter porperties. ## Handling custom comparison functions Our `bubble_sort` algorithm currently uses `operator<` to compare the elements to be sorted. To make it more generic, we would like it to work with any suitable comparison function instead, just like `std::sort`. Doing so is rather easy: ```cpp -template< - typename BidirectionalIterator, - typename StrictWeakOrdering -> +template auto bubble_sort(BidirectionalIterator first, BidirectionalIterator last, - StrictWeakOrdering compare) + Compare compare) -> void { while (first != last--) { - for (auto it = first ; it != last ; ++it) { + for (auto it = first; it != last; ++it) { auto next = std::next(it); if (compare(*next, *it)) { std::iter_swap(it, next); @@ -122,13 +125,13 @@ struct bubble_sorter_impl { template< typename BidirectionalIterator, - typename StrictWeakOrdering = std::less<> + typename Compare = std::less<> > auto operator()(BidirectionalIterator first, BidirectionalIterator last, - StrictWeakOrdering compare={}) const + Compare compare={}) const -> void { - bubble_sort(first, last, compare); + bubble_sort(first, last, std::move(compare)); } // Sorter traits @@ -137,23 +140,20 @@ struct bubble_sorter_impl }; ``` -With this addition, a `bubble_sorter` instance can be called with a custom comparison function or without one, defaulting to `std::less<>` when none is provided. Note that `sorter_facade` generates the appropriate `operator()` overloads so that the sorter can still be called with either a pair of iterators, a pair of iterators and a comparison function, a collection, or a collection and a comparison function. It also ensures that an instance of `bubble_sorter` can be converted to a function pointer corresponding to any of these overloads. +With this addition, a `bubble_sorter` instance can be called with a custom comparison function or without one, defaulting to `std::less<>` when none is provided. Note that [`sorter_facade`][sorter-facade] generates the appropriate `operator()` overloads so that the sorter can still be called with either a pair of iterators or a range, with or without a comparison function. It also ensures that an instance of `bubble_sorter` can be converted to a function pointer corresponding to any of those overloads. -It is possible to improve the comparison handling further by making the algorithm work out-of-the-box for pointer to member functions of the `lhs.compare_to(rhs)` kind. Transforming the passed comparison function with [`cppsort::utility::as_function`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#as_function) is enough to do the job: +The handling of comparison functions can be further improved by making the algorithm work out-of-the-box for pointer to member functions of the `lhs.compare_to(rhs)` kind. This can be done either by transforming the passed comparison function with [`cppsort::utility::as_function`][as-function] or by using [`std::invoke`][std-invoke] (C++17 feature): ```cpp -template< - typename BidirectionalIterator, - typename StrictWeakOrdering -> +template auto bubble_sort(BidirectionalIterator first, BidirectionalIterator last, - StrictWeakOrdering compare) + Compare compare) -> void { auto&& comp = cppsort::utility::as_function(compare); while (first != last--) { - for (auto it = first ; it != last ; ++it) { + for (auto it = first; it != last; ++it) { auto next = std::next(it); if (comp(*next, *it)) { std::iter_swap(it, next); @@ -163,19 +163,14 @@ auto bubble_sort(BidirectionalIterator first, BidirectionalIterator last, } ``` -Note that in C++17, it is preferred to use directly [`std::invoke`](https://en.cppreference.com/w/cpp/utility/functional/invoke) to call the comparison function instead of transforming it with `as_function`. - ## Using `bubble_sorter` with forward iterators -In its current state, `bubble_sort` isn't usable with forward iterators because of the `last--` instruction, which requires bidirectional iterators. If we drop this backwards iteration, we have to reintroduce the check to see whether the collection is sorted, which means that the algorithm will generally perform many useless comparisons all over the place. Fortunately, we can use another technique: we know that when the bubble sort traverses the collection for the *nth* time, it is supposed to perform exactly *n* comparisons. While decrementing a forward iterator isn't possible, we can still compute the size of the collection to sort, then decrement it and perform the correct number of comparisons: +In its current state, `bubble_sort` isn't usable with forward iterators because of the `last--` operation, which requires bidirectional iterators. In order to drop this backwards iteration without performing lots of extra useless operations, we can use the following technique: we know that when bubble sort traverses the collection for the *nth* time, it is supposed to perform *size - n* comparisons (the last *n* elements are already in order). While decrementing a forward iterator isn't possible, we can still compute the size of the collection to sort then decrement it and perform the correct number of comparisons: ```cpp -template< - typename ForwardIterator, - typename StrictWeakOrdering -> +template auto bubble_sort(ForwardIterator first, ForwardIterator last, - StrictWeakOrdering compare) + Compare compare) -> void { auto size = std::distance(first, last); @@ -184,9 +179,9 @@ auto bubble_sort(ForwardIterator first, ForwardIterator last, auto&& comp = cppsort::utility::as_function(compare); while (--size) { - ForwardIterator current = first; - ForwardIterator next = std::next(current); - for (std::size_t i = 0 ; i < size ; ++i) { + auto current = first; + auto next = std::next(current); + for (std::size_t i = 0; i < size; ++i) { if (comp(*next, *current)) { std::iter_swap(current, next); } @@ -201,16 +196,16 @@ The only change to make at the sorter level is to change its declared iterator c ## Handling projection parameters -Projections are functions that can be used to "view" the values to sort differently during the comparison. Most of the comparison sorters in **cpp-sort** take an optional projection parameter. The bubble sort being a comparison sorter, it may be interesting to have it handle projections too. In order to do that, we will have to alter both the sorting algorithm and the sorter. Fortunately, the modifications are pretty straigthforward: in the algorithm, we only have to add another parameter and use it on the values that are being compared: +[Projections][projections] are functions and function-like objects that can be used to "view" the values to sort differently during the comparison. Most of the comparison sorters in **cpp-sort** take an optional projection parameter. Our `bubble_sorter` being a comparison sorter, it may be interesting to have it handle projections too. In order to do that, we will have to alter both the sorting algorithm and the sorter. Fortunately, the modifications are pretty straigthforward: in the algorithm, we only have to add another parameter and use it on the values that are being compared: ```cpp template< typename ForwardIterator, - typename StrictWeakOrdering, + typename Compare, typename Projection > auto bubble_sort(ForwardIterator first, ForwardIterator last, - StrictWeakOrdering compare, Projection projection) + Compare compare, Projection projection) -> void { auto size = std::distance(first, last); @@ -220,9 +215,9 @@ auto bubble_sort(ForwardIterator first, ForwardIterator last, auto&& proj = cppsort::utility::as_function(projection); while (--size) { - ForwardIterator current = first; - ForwardIterator next = std::next(current); - for (std::size_t i = 0 ; i < size ; ++i) { + auto current = first; + auto next = std::next(current); + for (std::size_t i = 0; i < size; ++i) { if (comp(proj(*next), proj(*current))) { std::iter_swap(current, next); } @@ -233,24 +228,24 @@ auto bubble_sort(ForwardIterator first, ForwardIterator last, } ``` -Note the use of [`utility::as_function`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#as_function) to transform the projection parameter. While using the raw projection would have been enough in most scenarios, this line makes it possible to pass pointers to member data instead of functions so that the algorithm can sort the collection on a specific field; this is a rather powerful mechanism. Now, to the sorter: +Note the use of [`utility::as_function`][as-function] again to transform the projection parameter. While using the raw projection would have been enough in most scenarios, this line makes it possible to pass pointers to data members instead of functions to sort the collection on a specific field; this is a rather powerful mechanism. Now, to the sorter: ```cpp struct bubble_sorter_impl { template< typename ForwardIterator, - typename StrictWeakOrdering = std::less<>, + typename Compare = std::less<>, typename Projection = cppsort::utility::identity, typename = std::enable_if_t> > auto operator()(ForwardIterator first, ForwardIterator last, - StrictWeakOrdering compare={}, Projection projection={}) const + Compare compare={}, Projection projection={}) const -> void { - bubble_sort(first, last, compare, projection); + bubble_sort(first, last, std::move(compare), std::move(projection)); } // Sorter traits @@ -259,28 +254,28 @@ struct bubble_sorter_impl }; ``` -We can see several improvements compared to the previous version: first of all, we added an optional projection parameter which defauts to [`utility::identity`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#miscellaneous-function-objects). This is a function object that returns a value as is so that the default behaviour of the algorithm is to run *as if* projections didn't exist. It is very likely to be optimized aways by the compiler anyway. +We can see several improvements compared to the previous version: first of all, we added an optional projection parameter which defauts to [`utility::identity`][utility-identity] (in C++20 we would use [`std::identity`][std-identity]). This is a function object that takes a value and returns it as is so that the default behaviour of the algorithm is to run *as if* projections didn't exist. It is very likely to be optimized aways by the compiler. -The second modification is one I wish we could do without (yet another thing that concepts would make more expressive): [`is_projection_iterator_v`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-traits#is_projection-and-is_projection_iterator) is a trait that checks whether a projection function can be used on a dereferenced iterator. It also optionally checks that a given comparison function can be called with the result of two such projections. This trait exists to ensure that a sorter's `operator()` won't be called when these conditions are not satisfied, which may be crucial when aggregating sorters with [`hybrid_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#hybrid_adapter). +The second modification is one I wish we could do without (but will have to live with until concepts): [`is_projection_iterator_v`][is-projection] is a trait that checks whether a projection function can be used on a dereferenced iterator. It also optionally checks that a given comparison function can be called with the result of two such projections. This trait exists to ensure that a sorter's `operator()` won't be called when these conditions are not satisfied, which may be crucial when aggregating sorters with [`hybrid_adapter`][hybrid-adapter]. -Now that you know how to handle projections in your algorithm, here is the interesting part: you generally don't need to manually handle projections. The class template `sorter_facade` generates overloads of `operator()` taking projection functions that bake the projection into the comparison and forward that mix to the sorter implementation. In our implementation of `bubble_sort`, we always use the projection inside the comparison, so handling the projections by hand isn't giving us any optimization opportunity; we might as well implement just the comparison and add the small required SFINAE check: +Now that we saw how to handle projections in your algorithm, here is the interesting part: you generally don't need to manually handle projections. [`sorter_facade`][sorter-facade] generates overloads of `operator()` taking projection functions that bake the projection directly into the comparison and forward that mix to the sorter implementation. In our implementation of `bubble_sort`, we always use the projection inside the comparison, so handling the projections by hand isn't giving us any optimization opportunity; we might as well just implement the comparison and add the small required SFINAE check: ```cpp struct bubble_sorter_impl { template< typename ForwardIterator, - typename StrictWeakOrdering = std::less<>, + typename Compare = std::less<>, typename = std::enable_if_t> > auto operator()(ForwardIterator first, ForwardIterator last, - StrictWeakOrdering compare={}) const + Compare compare={}) const -> void { - // don't forget to roll back bubble_sort too - bubble_sort(first, last, compare); + // Don't forget to roll back bubble_sort too + bubble_sort(first, last, std::move(compare)); } // Sorter traits @@ -289,17 +284,18 @@ struct bubble_sorter_impl }; ``` -## Taking proxy iterators into account +## Proxy iterators -Generic agorithms are good, more generic algorithms are better. The current `bubble_sort` can already be used to sort well-formed every sequence container from the standard library, but it still might not be able to sort everything. Some kinds of iterators obey special rules when it comes to moving or swapping the referenced elements, or at least that's what they should do [in the future](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0022r1.html). Such iterators are currently known as "proxy iterators" and provide custom `iter_swap` and `iter_move` functions meant to be found by argument-dependent lookup. The functions needed to mak the whole thing work are not available in any standard library yet, so **cpp-sort** provides [`utility::iter_move` and `utility::iter_swap`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#iter_move-and-iter_swap) to replace them. Sorting algorithms should import them in the current scope and perform an unqualified call whenever they need to move or swap dereferenced iterators. +Generic agorithms are good, more generic algorithms are sometimes better. The current `bubble_sort` can already be used to sort every well-formed sequence container from the standard library, yet it still might not be able to sort everything: think of [`std::vector`][std-vector-bool] where you're not swapping actual values, but proxy objects representing the stored values - though implementations sometimes "make it work". + +C++20 ranges introduces the notion of ["proxy iterators"][proxy-iterators], which are basically iterators that can't yield a proper reference to the object they point to, but instead yield a proxy object acting as a reference. In order to handle such iterators, C++20 introduces the *customization point objects* [`std::ranges::iter_move`][std-iter-move] and [`std::ranges::iter_swap`][std-iter-swap] which should be used instead of `std::move(*it)` and `std::iter_swap(it1, it2)` in generic algorithms that aim to support proxy iterators. + +**cpp-sort** being a C++14 library, it can't rely on these CPOs and provides the utility functions [`utility::iter_move` and `utility::iter_swap`][utility-iter-move] to replace them. They are a bit cruder than their standard equivalents you have to import them into the current namespace and perform an unqualified call, *à la* `std::swap`. ```cpp -template< - typename ForwardIterator, - typename StrictWeakOrdering -> +template auto bubble_sort(ForwardIterator first, ForwardIterator last, - StrictWeakOrdering compare) + Compare compare) -> void { auto size = std::distance(first, last); @@ -308,9 +304,9 @@ auto bubble_sort(ForwardIterator first, ForwardIterator last, auto&& comp = cppsort::utility::as_function(compare); while (--size) { - ForwardIterator current = first; - ForwardIterator next = std::next(current); - for (std::size_t i = 0 ; i < size ; ++i) { + auto current = first; + auto next = std::next(current); + for (std::size_t i = 0; i < size; ++i) { if (comp(*next, *current)) { using cppsort::utility::iter_swap; iter_swap(current, next); @@ -322,19 +318,14 @@ auto bubble_sort(ForwardIterator first, ForwardIterator last, } ``` -While this improvement shouldn't make a difference most of the time, implementing it is pretty straightforward, incurs no performance cost, and ensures that the algorithm is future-proof and ready for the next standards. - ## Final optimizations -Our current version of `bubble_sort` has to compute the size of the collection to sort prior to the actual sort. This is not optimal since some of the containers in the standard library know their size and can provide it in O(1) time, while computing the distance between two iterators might be O(n) depending on their iterator category. **cpp-sort** makes it possible to easily use this information: the function [`utility::size`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#size) takes a container and returns the result of the member function `size` if the container has one, or `std::distance(std::begin(container), std::end(container))` instead if it doesn't have such a member function. This small tool allows us to rewrite `bubble_sort` and `bubble_sorter` so that they can take advantage of this information when available: +Our current version of `bubble_sort` has to compute the size of the collection to sort prior to the actual sort. This is not optimal since some containers such as [`std::list`][std-list] know their size and can provide it in O(1) time, while computing the distance between two iterators would be O(n) time. **cpp-sort** makes it possible to easily use this information: the function [`utility::size`][utility-size] takes a container and returns the result of the member function `size` if the container has one, or `std::distance(std::begin(container), std::end(container))` otherwise. This tool allows us to rewrite `bubble_sort` and `bubble_sorter` so that they can take advantage of this information when available: ```cpp -template< - typename ForwardIterator, - typename StrictWeakOrdering -> +template auto bubble_sort(ForwardIterator first, std::size_t size, - StrictWeakOrdering compare) + Compare compare) -> void { if (size < 2) return; @@ -342,9 +333,9 @@ auto bubble_sort(ForwardIterator first, std::size_t size, auto&& comp = cppsort::utility::as_function(compare); while (--size) { - ForwardIterator current = first; - ForwardIterator next = std::next(current); - for (std::size_t i = 0 ; i < size ; ++i) { + auto current = first; + auto next = std::next(current); + for (std::size_t i = 0; i < size; ++i) { if (compare(*next, *current)) { using cppsort::utility::iter_swap; iter_swap(current, next); @@ -360,33 +351,33 @@ struct bubble_sorter_impl // Pair of iterators overload template< typename ForwardIterator, - typename StrictWeakOrdering = std::less<>, + typename Compare = std::less<>, typename = std::enable_if_t> > auto operator()(ForwardIterator first, ForwardIterator last, - StrictWeakOrdering compare={}) const + Compare compare={}) const -> void { bubble_sort(first, std::distance(first, last), - compare); + std::move(compare)); } // Iterable overload template< typename ForwardIterable, - typename StrictWeakOrdering = std::less<>, + typename Compare = std::less<>, typename = std::enable_if_t> > - auto operator()(ForwardIterable&& iterable, StrictWeakOrdering compare={}) const + auto operator()(ForwardIterable&& iterable, Compare compare={}) const -> void { bubble_sort(std::begin(iterable), cppsort::utility::size(iterable), - compare); + std::move(compare)); } // Sorter traits @@ -395,11 +386,11 @@ struct bubble_sorter_impl }; ``` -We used forwarding references to ensure that the range overload will work with lvalue ranges, but also with temporary span-like classes (*e.g. `gsl::span`). Note that `sorter_facade` will make sure to call the new `operator()` overload for ranges instead of dispatching the call to the overload that takes a pair of iterators when given a full collection, so everything should work smoothly enough. +We use forwarding references to ensure that the range overload works with lvalue ranges, but also with temporary [`std::span`][std-span]-like classes. Note that [`sorter_facade`][sorter-facade] will call the new `operator()` overload for ranges instead of dispatching the call to the overload that takes a pair of iterators when given a full collection, so everything should work smoothly enough. ## Instantiating the sorter -The sorter abstraction is useful, but most of the time we only need a sorting algorithms. Therefore, it might be a good idea to instantiate the `bubble_sorter` and to have a global `bubble_sort` instance, more versatile than the original `bubble_sort` algorithm. Declaring global function objects while avoiding ODR problems is a bit tricky pre-C++17 due to the lack of `inline` variables; after having put the original `bubble_sort` and `bubble_sorter_impl` in a `detail` namespace to avoid naming problems, we will create a global instance of `bubble_sorter` with the following pattern: +The sorter abstraction is useful, but most of the time we only need a sorting algorithm. Therefore, it might be a good idea to instantiate `bubble_sorter` and to have a global `bubble_sort` instance, more versatile than the original `bubble_sort` algorithm. ```cpp // C++14 @@ -413,13 +404,13 @@ namespace inline constexpr auto&& bubble_sort = bubble_sorter{}; ``` -The utility [`static_const`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#static_const) is a variable template used to avoid ODR problem. Understanding the details is a bit tough; you can read [Eric Niebler's original article](https://ericniebler.com/2014/10/21/customization-point-design-in-c11-and-beyond/) about this pattern if you want to learn more about it. Basically it is a poor man's substitute to compensate the lack of `inline` variables pre-C++17. +The combination of [`utility::static_const`][utility-static-const] with an anonymous namespace is a trick used to avoid ODR problems; you can read more about how and why it works in [Eric Niebler's original article](https://ericniebler.com/2014/10/21/customization-point-design-in-c11-and-beyond/). It is basically a poor man's substitute to compensate the lack of `inline` variables pre-C++17. ## Better error messages -We now have a versatile `bubble_sorter`, able to handle many scenarios, and optimized as much as a bubble sort can be. It works really well... until it doesn't. **cpp-sort** has one major drawback (some would say more than one, but...): when not used correctly, the error messages are close to unreadable; forget one `const` and embrace the hundreds of lines of cryptic SFINAE error messages, and I really mean it! It sometimes took me far longer than it should have to find bugs. The sorter works properly, but we can still improve the way it fails... +We now have a versatile `bubble_sorter`, able to handle many scenarios, and optimized as much as a bubble sort can be without turning it into a different algorithm. It works really well... until it doesn't. **cpp-sort** has one major drawback there: when not used correctly, the error messages are often close to unreadable; forget one `const` and embrace the hundreds of lines of cryptic SFINAE error messages, and I really mean it!. The sorter works properly, but we can still somewhat improve the way it fails. -Starting easy: we can use strong `typedef`s to hide some irrelevant template parameters and shorten some error messages a bit. In our case, we can make `bubble_sorter` *inherit* from `cppsort::sorter_facade` instead of defining it as a type alias. It doesn't improve error messages that much, but at least they will use the name `bubble_sorter` until they have to display the full name. +Starting easy: we can use strong `typedef`s to hide some irrelevant template parameters and shorten some error messages a bit. In our case, we can make `bubble_sorter` *inherit* from `cppsort::sorter_facade` instead of defining it as a type alias. It doesn't improve error messages all that much, but at least they will show the name `bubble_sorter` as long as they have to display the full name. ```cpp struct bubble_sorter: @@ -427,18 +418,43 @@ struct bubble_sorter: {}; ``` -One small change that can greatly improve error messages is the addition of a static assertion in `operator()` to assert that the iterator category of the passed collection is compatible with that of the sorter. It doesn't have that much of an impact with `bubble_sorter` since we designed it to work with forward iterators (I hardly see why would anyone pass simple input iterators to it), but it's good practice anyway if you design sorters that only work with more restricted iterator categories. For example, passing an `std::list` to [`heap_sorter`](https://github.com/Morwenn/cpp-sort/wiki/Sorters#heap_sorter) used to spawn more than 70 lines of cryptic error messages with g++ 5.2 (basically, it failed when it encoutered an operation in the heapsort implementation not compatible with bidirectional iterators); with such a static assertion, it came down to 4 lines: the 4th line was the static assertion message, and the one above referenced the faulty call site, which is quite a big improvement. +Another small change that can greatly improve error messages is the addition of a static assertion in `operator()` to assert that the iterator category of the passed collection is compatible with that of the sorter. It doesn't have that much of an impact with `bubble_sorter` since we designed it to work with forward iterators, but it's good practice anyway if you design sorters that only work with more restricted iterator categories. For example, passing an [`std::list`][std-list] to [`heap_sorter`][heap-sorter] used to spawn more than 70 lines of cryptic error messages with g++ 5.2 (basically, it failed when it encoutered an operation in the heapsort implementation not compatible with bidirectional iterators); with such a static assertion, it came down to 4 lines: the 4th line was the static assertion message, and the one above referenced the faulty call site, which is quite a big improvement. ```cpp static_assert( std::is_base_of< - std::forward_iterator_tag, + iterator_category, typename std::iterator_traits::iterator_category >::value, "bubble_sorter requires at least forward iterators" ); ``` -Concepts would probably improve some error messages too, but will have wait for at least C++20. In the current state of the library, many error messages remain pretty noisy and tough to understand, and we can't realistically put static assertions all over the place because too many things rely on SFINAE. That is why even these small improvements to error messages matter; if one can't understand why something fails, they are less likely to fix the error. - -And that's it: we have covered pretty much every interesting aspect of writing a simple comparison sorter and we have seen how to implement some small optimizations and care about error messages as well as how to enhance it with projections. I hope you enjoyed the tutorial, even if bubble sort is not the most interesting sorting algorithm around. You can find the full implementation in the examples folder :) +Concepts might improve some error messages too, but they're out of scope for **cpp-sort** 1.x. In the current state of the library, many error messages remain pretty noisy and tough to understand, and we can't realistically put static assertions all over the place because too many things rely on SFINAE. That is why even these small improvements to error messages matter: if one can't understand why something fails, they are less likely to fix the error. + +## Conclusion + +That's it: we have covered pretty much every interesting aspect of writing a simple comparison sorter. I hope you enjoyed the tutorial, even if bubble sort is not the most interesting sorting algorithm around. You can find the full implementation in the examples folder :) + + + [as-function]: https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#as_function + [bubble-sorter]: https://en.wikipedia.org/wiki/Bubble_sort + [heap-sorter]: https://github.com/Morwenn/cpp-sort/wiki/Sorters#heap_sorter + [hybrid-adapter]: https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#hybrid_adapter + [is-projection]: https://github.com/Morwenn/cpp-sort/wiki/Sorter-traits#is_projection-and-is_projection_iterator + [projections]: https://ezoeryou.github.io/blog/article/2019-01-22-ranges-projection.html + [proxy-iterators]: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0022r2.html + [sorter-facade]: https://github.com/Morwenn/cpp-sort/wiki/Sorter-facade + [sorter-traits]: https://github.com/Morwenn/cpp-sort/wiki/Sorter-traits#sorter_traits + [stable-adapter]: https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#stable_adapter-make_stable-and-stable_t + [std-identity]: https://en.cppreference.com/w/cpp/utility/functional/identity + [std-invoke]: https://en.cppreference.com/w/cpp/utility/functional/invoke + [std-iter-move]: https://en.cppreference.com/w/cpp/iterator/ranges/iter_move + [std-iter-swap]: https://en.cppreference.com/w/cpp/iterator/ranges/iter_swap + [std-list]: https://en.cppreference.com/w/cpp/container/list + [std-span]: https://en.cppreference.com/w/cpp/container/span + [std-vector-bool]: https://en.cppreference.com/w/cpp/container/vector_bool + [utility-identity]: https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#miscellaneous-function-objects + [utility-iter-move]: https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#iter_move-and-iter_swap + [utility-size]: https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#size + [utility-static-const]: https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#static_const diff --git a/examples/bubble_sorter.cpp b/examples/bubble_sorter.cpp index c62fd865..a2325d25 100644 --- a/examples/bubble_sorter.cpp +++ b/examples/bubble_sorter.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2017 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -15,12 +15,8 @@ namespace detail { - template< - typename ForwardIterator, - typename StrictWeakOrdering - > - auto bubble_sort(ForwardIterator first, std::size_t size, - StrictWeakOrdering compare) + template + auto bubble_sort(ForwardIterator first, std::size_t size, Compare compare) -> void { if (size < 2) return; @@ -28,9 +24,9 @@ namespace detail auto&& comp = cppsort::utility::as_function(compare); while (--size) { - ForwardIterator current = first; - ForwardIterator next = std::next(current); - for (std::size_t i = 0 ; i < size ; ++i) { + auto current = first; + auto next = std::next(current); + for (std::size_t i = 0; i < size; ++i) { if (comp(*next, *current)) { using cppsort::utility::iter_swap; iter_swap(current, next); @@ -46,18 +42,17 @@ namespace detail // Pair of iterators overload template< typename ForwardIterator, - typename StrictWeakOrdering = std::less<>, + typename Compare = std::less<>, typename = std::enable_if_t> > - auto operator()(ForwardIterator first, ForwardIterator last, - StrictWeakOrdering compare={}) const + auto operator()(ForwardIterator first, ForwardIterator last, Compare compare={}) const -> void { static_assert( std::is_base_of< - std::forward_iterator_tag, + iterator_category, typename std::iterator_traits::iterator_category >::value, "bubble_sorter requires at least forward iterators" @@ -70,17 +65,17 @@ namespace detail // Iterable overload template< typename ForwardIterable, - typename StrictWeakOrdering = std::less<>, + typename Compare = std::less<>, typename = std::enable_if_t> > - auto operator()(ForwardIterable&& iterable, StrictWeakOrdering compare={}) const + auto operator()(ForwardIterable&& iterable, Compare compare={}) const -> void { static_assert( std::is_base_of< - std::forward_iterator_tag, + iterator_category, typename std::iterator_traits::iterator_category >::value, "bubble_sorter requires at least forward iterators" @@ -118,7 +113,7 @@ int main() // Fill the collection in sorted order std::array collection; - std::iota(std::begin(collection), std::end(collection), 0); + std::iota(collection.begin(), collection.end(), 0); // Projection to sort in descending order auto projection = [](int n) { return -n; }; @@ -130,6 +125,6 @@ int main() // Bubble sort the collection bubble_sort(to_sort, projection); // Check that it is sorted in descending order - assert(std::is_sorted(std::begin(to_sort), std::end(to_sort), std::greater<>{})); - } while (std::next_permutation(std::begin(collection), std::end(collection))); + assert(std::is_sorted(to_sort.begin(), to_sort.end(), std::greater<>{})); + } while (std::next_permutation(collection.begin(), collection.end())); } diff --git a/include/cpp-sort/adapters/verge_adapter.h b/include/cpp-sort/adapters/verge_adapter.h index 7c9cc4b1..434394db 100644 --- a/include/cpp-sort/adapters/verge_adapter.h +++ b/include/cpp-sort/adapters/verge_adapter.h @@ -51,7 +51,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::random_access_iterator_tag, + iterator_category, iterator_category_t >::value, "verge_adapter requires at least random-access iterators" diff --git a/include/cpp-sort/detail/associate_iterator.h b/include/cpp-sort/detail/associate_iterator.h index 3fde94f1..71d1de77 100644 --- a/include/cpp-sort/detail/associate_iterator.h +++ b/include/cpp-sort/detail/associate_iterator.h @@ -196,7 +196,7 @@ namespace detail auto operator*() const -> decltype(*base()) { - return *base(); + return *_it; } CPPSORT_ATTRIBUTE_NODISCARD @@ -256,18 +256,11 @@ namespace detail //////////////////////////////////////////////////////////// // Elements access operators - CPPSORT_ATTRIBUTE_NODISCARD - auto operator[](difference_type pos) - -> decltype(base()[pos]) - { - return base()[pos]; - } - CPPSORT_ATTRIBUTE_NODISCARD auto operator[](difference_type pos) const -> decltype(base()[pos]) { - return base()[pos]; + return _it[pos]; } //////////////////////////////////////////////////////////// diff --git a/include/cpp-sort/detail/bitops.h b/include/cpp-sort/detail/bitops.h index 26681760..817f870a 100644 --- a/include/cpp-sort/detail/bitops.h +++ b/include/cpp-sort/detail/bitops.h @@ -11,6 +11,7 @@ #include #include #include +#include "../detail/attributes.h" #include "../detail/config.h" #include "../detail/type_traits.h" @@ -18,6 +19,15 @@ namespace cppsort { namespace detail { + // Cast signed value to unsigned one + template + CPPSORT_ATTRIBUTE_NODISCARD + constexpr auto as_unsigned(Integer value) + -> std::make_unsigned_t + { + return static_cast>(value); + } + // Returns 2^floor(log2(n)), assumes n > 0 template constexpr auto hyperfloor(Unsigned n) @@ -96,7 +106,7 @@ namespace detail constexpr auto half(Integer value) -> detail::enable_if_t::value, Integer> { - return static_cast(static_cast>(value) / 2); + return static_cast(as_unsigned(value) / 2); } template @@ -114,7 +124,8 @@ namespace detail constexpr auto has_single_bit(Integer n) noexcept -> bool { - auto x = static_cast>(n); + CPPSORT_ASSERT(n >= 0); + auto x = as_unsigned(n); return x != 0 && (x & (x - 1)) == 0; } diff --git a/include/cpp-sort/detail/drop_merge_sort.h b/include/cpp-sort/detail/drop_merge_sort.h index 0ebe7a0a..e10ad312 100644 --- a/include/cpp-sort/detail/drop_merge_sort.h +++ b/include/cpp-sort/detail/drop_merge_sort.h @@ -51,8 +51,9 @@ namespace detail { using utility::iter_move; - auto size = std::distance(begin, end); - if (size < 2) return; + if (begin == end || std::next(begin) == end) { + return; + } auto&& comp = utility::as_function(compare); auto&& proj = utility::as_function(projection); diff --git a/include/cpp-sort/detail/immovable_vector.h b/include/cpp-sort/detail/immovable_vector.h index a81d0a59..274d8334 100644 --- a/include/cpp-sort/detail/immovable_vector.h +++ b/include/cpp-sort/detail/immovable_vector.h @@ -45,7 +45,7 @@ namespace detail //////////////////////////////////////////////////////////// // Construction - immovable_vector(std::ptrdiff_t n): + explicit immovable_vector(std::ptrdiff_t n): capacity_(n), memory_( static_cast(::operator new(n * sizeof(T))) diff --git a/include/cpp-sort/detail/merge_insertion_sort.h b/include/cpp-sort/detail/merge_insertion_sort.h index b4496284..414d1da0 100644 --- a/include/cpp-sort/detail/merge_insertion_sort.h +++ b/include/cpp-sort/detail/merge_insertion_sort.h @@ -79,7 +79,7 @@ namespace detail auto operator*() const -> reference { - return _it[_size - 1]; + return *std::next(_it, _size - 1); } CPPSORT_ATTRIBUTE_NODISCARD @@ -110,7 +110,7 @@ namespace detail auto operator--() -> group_iterator& { - _it -= _size; + std::advance(_it, -_size); return *this; } @@ -125,7 +125,7 @@ namespace detail auto operator+=(difference_type increment) -> group_iterator& { - std::advance(_it, _size * increment); + _it += _size * increment; return *this; } @@ -139,18 +139,11 @@ namespace detail //////////////////////////////////////////////////////////// // Elements access operators - CPPSORT_ATTRIBUTE_NODISCARD - auto operator[](difference_type pos) - -> decltype(base()[pos * size() + size() - 1]) - { - return base()[pos * size() + size() - 1]; - } - CPPSORT_ATTRIBUTE_NODISCARD auto operator[](difference_type pos) const - -> decltype(base()[pos * size() + size() - 1]) + -> decltype(base()[pos]) { - return base()[pos * size() + size() - 1]; + return _it[pos * _size + _size - 1]; } //////////////////////////////////////////////////////////// @@ -241,7 +234,7 @@ namespace detail friend auto iter_swap(group_iterator lhs, group_iterator rhs) -> void { - detail::swap_ranges_inner(lhs.base(), lhs.base() + lhs.size(), rhs.base()); + detail::swap_ranges_inner(lhs.base(), std::next(lhs.base(), lhs.size()), rhs.base()); } private: @@ -255,7 +248,7 @@ namespace detail template CPPSORT_ATTRIBUTE_NODISCARD - auto make_group_iterator(Iterator it, difference_type_t> size) + auto make_group_iterator(Iterator it, difference_type_t size) -> group_iterator { return { it, size }; @@ -263,10 +256,11 @@ namespace detail template CPPSORT_ATTRIBUTE_NODISCARD - auto make_group_iterator(group_iterator it, difference_type_t> size) + auto make_group_iterator(group_iterator it, difference_type_t size) -> group_iterator { - return { it.base(), size * it.size() }; + size *= it.size(); + return { it.base(), size }; } //////////////////////////////////////////////////////////// diff --git a/include/cpp-sort/detail/poplar_sort.h b/include/cpp-sort/detail/poplar_sort.h index 2c8d7e00..5f5093cb 100644 --- a/include/cpp-sort/detail/poplar_sort.h +++ b/include/cpp-sort/detail/poplar_sort.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2020 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_POPLAR_SORT_H_ @@ -104,8 +104,7 @@ namespace detail Compare compare, Projection projection) -> void { - using poplar_size_t = std::make_unsigned_t>; - poplar_size_t size = last - first; + auto size = as_unsigned(last - first); if (size < 16) { // A sorted collection is a valid poplar heap; // when the heap is small, using insertion sort @@ -176,7 +175,7 @@ namespace detail if (poplars.back().size == 1) return; auto& back = poplars.back(); auto old_end = back.end; - auto new_size = (back.size - 1) / 2; + poplar_size_t new_size = (back.size - 1) / 2; auto middle = back.begin + new_size; back.end = middle; back.size = new_size; @@ -185,7 +184,7 @@ namespace detail } else { auto& back = poplars.back(); auto old_end = back.end; - auto new_size = (back.size - 1) / 2; + poplar_size_t new_size = (back.size - 1) / 2; auto middle = back.begin + new_size; back.end = middle; back.size = new_size; diff --git a/include/cpp-sort/detail/quicksort.h b/include/cpp-sort/detail/quicksort.h index abb0a617..8afda657 100644 --- a/include/cpp-sort/detail/quicksort.h +++ b/include/cpp-sort/detail/quicksort.h @@ -62,6 +62,7 @@ namespace detail Compare compare, Projection projection) -> void { + using difference_type = difference_type_t; using utility::iter_swap; // If the collection is small enough, fall back to @@ -81,7 +82,7 @@ namespace detail // Put the pivot at position std::prev(last) and partition iter_swap(median_it, last_1); auto&& pivot1 = proj(*last_1); - ForwardIterator middle1 = detail::partition( + auto middle1 = detail::partition( first, last_1, [&](auto&& elem) { return comp(proj(elem), pivot1); } ); @@ -89,7 +90,7 @@ namespace detail // Put the pivot in its final position and partition iter_swap(middle1, last_1); auto&& pivot2 = proj(*middle1); - ForwardIterator middle2 = detail::partition( + auto middle2 = detail::partition( std::next(middle1), last, [&](auto&& elem) { return not comp(pivot2, proj(elem)); } ); @@ -99,7 +100,7 @@ namespace detail // right one, so computing its size should generally be cheaper auto size_left = std::distance(first, middle1); auto size_middle = std::distance(middle1, middle2); - auto size_right = size - size_left - size_middle; + difference_type size_right = size - size_left - size_middle; // Recurse in the smallest partition first to limit the call // stack overhead diff --git a/include/cpp-sort/detail/slabsort.h b/include/cpp-sort/detail/slabsort.h index ac046e16..9a9b3618 100644 --- a/include/cpp-sort/detail/slabsort.h +++ b/include/cpp-sort/detail/slabsort.h @@ -12,6 +12,7 @@ #include #include #include +#include #include "bitops.h" #include "fixed_size_list.h" #include "functional.h" @@ -55,19 +56,19 @@ namespace detail }; }; - template - auto slabsort_get_median(RandomAccessIterator first, RandomAccessIterator last, - immovable_vector& iterators_buffer, + template + auto slabsort_get_median(BidirectionalIterator first, + difference_type_t size, + immovable_vector& iterators_buffer, Compare compare, Projection projection) - -> RandomAccessIterator + -> BidirectionalIterator { - using difference_type = difference_type_t; - auto size = last - first; + using difference_type = difference_type_t; //////////////////////////////////////////////////////////// - // Bind index to iterator + // Indirectly partition the iterators - // Associate iterators to their position + // Copy the iterators in a vector iterators_buffer.clear(); for (difference_type count = 0; count != size; ++count) { iterators_buffer.emplace_back(first); @@ -83,12 +84,14 @@ namespace detail ); } - template - auto slabsort_partition(RandomAccessIterator first, RandomAccessIterator last, - immovable_vector& iterators_buffer, + template + auto slabsort_partition(BidirectionalIterator first, BidirectionalIterator last, + difference_type_t size, + immovable_vector& iterators_buffer, Compare compare, Projection projection) -> void { + using utility::iter_swap; auto&& comp = utility::as_function(compare); auto&& proj = utility::as_function(projection); @@ -99,14 +102,14 @@ namespace detail // difference should not have any noticeable impact on the // adaptivity to presortedness - auto pivot = slabsort_get_median(first, last, iterators_buffer, compare, projection); + auto pivot = slabsort_get_median(first, size, iterators_buffer, compare, projection); auto last_1 = std::prev(last); // Put the pivot at position std::prev(last) and partition iter_swap(pivot, last_1); auto&& pivot1 = proj(*last_1); auto middle1 = detail::stable_partition( - first, last_1, + first, last_1, size, [&](auto&& elem) { return comp(proj(elem), pivot1); } ); @@ -114,15 +117,15 @@ namespace detail iter_swap(middle1, last_1); } - template - auto try_melsort(RandomAccessIterator first, RandomAccessIterator last, - difference_type_t p, - fixed_size_list_node_pool>& node_pool, + template + auto try_melsort(BidirectionalIterator first, BidirectionalIterator last, + difference_type_t p, + fixed_size_list_node_pool>& node_pool, Compare compare, Projection projection) -> bool { - using rvalue_type = rvalue_type_t; - using node_type = slabsort_list_node; + using rvalue_type = rvalue_type_t; + using node_type = slabsort_list_node; using utility::iter_move; auto&& comp = utility::as_function(compare); auto&& proj = utility::as_function(projection); @@ -133,9 +136,9 @@ namespace detail // Encroaching lists std::vector> lists; - lists.emplace_back(node_pool, destroy_node_contents); + lists.emplace_back(node_pool, destroy_node_contents); lists.back().push_back([&first](node_type* node) { - ::new (&node->it) RandomAccessIterator(first); + ::new (&node->it) BidirectionalIterator(first); }); //////////////////////////////////////////////////////////// @@ -154,7 +157,7 @@ namespace detail } ); insertion_point->push_back([&it](node_type* node) { - ::new (&node->it) RandomAccessIterator(it); + ::new (&node->it) BidirectionalIterator(it); }); } else if (not comp(proj(*last_list.begin().base()->it), value)) { // Element belongs to the heads (smaller elements) @@ -165,21 +168,21 @@ namespace detail } ); insertion_point->push_front([&it](node_type* node) { - ::new (&node->it) RandomAccessIterator(it); + ::new (&node->it) BidirectionalIterator(it); }); } else { // Element does not belong to the existing encroaching lists, // create a new list for it - lists.emplace_back(node_pool, destroy_node_contents); + lists.emplace_back(node_pool, destroy_node_contents); lists.back().push_back([&it](node_type* node) { - ::new (&node->it) RandomAccessIterator(it); + ::new (&node->it) BidirectionalIterator(it); }); } // Too many encroaching lists have been created (Enc > p), // give up, the elements of the collection remain in their // order of creation - using difference_type = difference_type_t; + using difference_type = difference_type_t; if (difference_type(lists.size()) >= p) { return false; } @@ -209,13 +212,13 @@ namespace detail return true; } - template - auto slabsort_impl(RandomAccessIterator first, RandomAccessIterator last, - difference_type_t size, - difference_type_t original_p, - difference_type_t current_p, - immovable_vector& iterators_buffer, - fixed_size_list_node_pool>& node_pool, + template + auto slabsort_impl(BidirectionalIterator first, BidirectionalIterator last, + difference_type_t size, + difference_type_t original_p, + difference_type_t current_p, + immovable_vector& iterators_buffer, + fixed_size_list_node_pool>& node_pool, Compare compare, Projection projection) -> void { @@ -223,10 +226,10 @@ namespace detail return; } - slabsort_partition(first, last, iterators_buffer, compare, projection); - auto left_size = (last - first) / 2; + slabsort_partition(first, last, size, iterators_buffer, compare, projection); + auto left_size = size / 2; auto right_size = size - left_size; - auto middle = first + left_size; + auto middle = std::next(first, left_size); if (current_p > 2) { // Partition further until the partitions are small enough slabsort_impl(first, middle, left_size, original_p, current_p / 2, @@ -251,18 +254,18 @@ namespace detail } } - template - auto slabsort(RandomAccessIterator first, RandomAccessIterator last, + template + auto slabsort(BidirectionalIterator first, BidirectionalIterator last, + difference_type_t size, Compare compare, Projection projection) -> void { - auto size = last - first; if (size < 2) { return; } // Node pool used by all try_melsort invocations - using node_type = slabsort_list_node; + using node_type = slabsort_list_node; fixed_size_list_node_pool node_pool(size); // Take advantage of existing presortedness once before the partitioning @@ -275,9 +278,9 @@ namespace detail } // Allocate a buffer that will be used for median finding - immovable_vector iterators_buffer(size); + immovable_vector iterators_buffer(size); - difference_type_t original_p = 2; + difference_type_t original_p = 2; return slabsort_impl( first, last, size, original_p, original_p, iterators_buffer, node_pool, diff --git a/include/cpp-sort/detail/spinsort.h b/include/cpp-sort/detail/spinsort.h index ee4f99a1..9968f86b 100644 --- a/include/cpp-sort/detail/spinsort.h +++ b/include/cpp-sort/detail/spinsort.h @@ -20,12 +20,10 @@ #define CPPSORT_DETAIL_SPINSORT_H_ #include -#include #include #include #include #include -#include #include #include #include "boost_common/util/merge.h" @@ -33,6 +31,7 @@ #include "bitops.h" #include "config.h" #include "functional.h" +#include "immovable_vector.h" #include "insertion_sort.h" #include "is_sorted_until.h" #include "iterator_traits.h" @@ -72,6 +71,7 @@ namespace detail const range& rng_aux) -> void { + using difference_type = difference_type_t; using utility::iter_move; CPPSORT_ASSERT(last - mid <= rng_aux.size()); @@ -86,24 +86,26 @@ namespace detail // sorted part // the data are inserted in rng_aux //----------------------------------------------------------------------- - std::vector viter; auto data = rng_aux.first; detail::move(mid, last, data); auto ndata = last - mid; + immovable_vector viter(ndata + 1); - RandomAccessIterator1 linf = first, lsup = mid; - for (std::uint32_t i = 0 ; i < ndata ; ++i) { + auto linf = first; + auto lsup = mid; + for (difference_type i = 0; i < ndata; ++i) { auto it1 = detail::upper_bound(linf, lsup, proj(*(data + i)), compare, projection); - viter.push_back(it1); + viter.emplace_back(it1); linf = it1; } - viter.push_back(mid); + viter.emplace_back(mid); // moving the elements - for (std::uint32_t i = viter.size() - 1; i != 0; --i) { - RandomAccessIterator1 src = viter[i], limit = viter[i - 1]; - RandomAccessIterator1 dest = src + i; + for (auto i = ndata; i != 0; --i) { + auto src = viter[i]; + auto limit = viter[i - 1]; + auto dest = src + i; while (src != limit) { *(--dest) = iter_move(--src); } @@ -196,7 +198,8 @@ namespace detail //----------------------------------------------------------------------------- template auto range_sort(const range& range1, const range& range2, - Compare compare, Projection projection, std::uint32_t level) + Compare compare, Projection projection, + std::make_unsigned_t> level) -> void { using range_it1 = range; @@ -249,17 +252,20 @@ namespace detail Compare compare, Projection projection) -> void { + using difference_type = difference_type_t; + // minimal number of element before to jump to insertionsort - static const std::uint32_t sort_min = 32; + constexpr difference_type sort_min = 32; if (rng_data.size() <= sort_min) { insertion_sort(rng_data.first, rng_data.last, compare, projection); return; } - CPPSORT_ASSERT(rng_aux.size () >= rng_data.size()); + CPPSORT_ASSERT(rng_aux.size() >= rng_data.size()); range rng_buffer(rng_aux.first, rng_aux.first + rng_data.size()); - CPPSORT_ASSERT(((rng_data.size() + sort_min - 1) / sort_min) - 1 > 0); - std::uint32_t nlevel = detail::log2(((rng_data.size() + sort_min - 1) / sort_min) - 1) + 1; + auto nlevel_1 = ((rng_data.size() + sort_min - 1) / sort_min) - 1; + CPPSORT_ASSERT(nlevel_1 > 0); + auto nlevel = as_unsigned(detail::log2(nlevel_1) + 1); if ((nlevel & 1) == 0) { range_sort(rng_buffer, rng_data, compare, projection, nlevel); @@ -284,11 +290,12 @@ namespace detail using range_it = range; using rvalue_type = rvalue_type_t; + using difference_type = difference_type_t; using range_buf = range; // When the number of elements to sort is smaller than Sort_min, are sorted // by the insertion sort algorithm - static constexpr std::uint32_t Sort_min = 36; + static constexpr difference_type Sort_min = 36; public: @@ -334,7 +341,7 @@ namespace detail //--------------------------------------------------------------------- // Process //--------------------------------------------------------------------- - std::uint32_t nlevel = detail::log2(((size + Sort_min - 1) / Sort_min) - 1); + auto nlevel = as_unsigned(detail::log2(((size + Sort_min - 1) / Sort_min) - 1)); CPPSORT_ASSERT(nlevel != 0); if ((nlevel & 1) == 1) { diff --git a/include/cpp-sort/detail/stable_partition.h b/include/cpp-sort/detail/stable_partition.h index 67e54f57..66ff98b5 100644 --- a/include/cpp-sort/detail/stable_partition.h +++ b/include/cpp-sort/detail/stable_partition.h @@ -136,7 +136,9 @@ namespace detail } template - auto stable_partition(BidirectionalIterator first, BidirectionalIterator last, Predicate predicate) + auto stable_partition(BidirectionalIterator first, BidirectionalIterator last, + difference_type_t size, + Predicate predicate) -> BidirectionalIterator { using difference_type = difference_type_t; @@ -153,11 +155,13 @@ namespace detail if (not pred(*first)) { break; } + --size; ++first; } // first points to first false, everything prior to first is already set. // Either prove [first, last) is all false and return first, or point last to last true do { + --size; if (first == --last) { return first; } @@ -166,7 +170,7 @@ namespace detail // *first is known to be false // *last is known to be true // len >= 2 - auto len = std::distance(first, last) + 1; + auto len = size + 1; temporary_buffer> buffer(nullptr); if (len >= alloc_limit) { buffer.try_grow(len); diff --git a/include/cpp-sort/sorters/block_sorter.h b/include/cpp-sort/sorters/block_sorter.h index e8302b54..d1a17148 100644 --- a/include/cpp-sort/sorters/block_sorter.h +++ b/include/cpp-sort/sorters/block_sorter.h @@ -46,7 +46,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::random_access_iterator_tag, + iterator_category, iterator_category_t >::value, "block_sorter requires at least random-access iterators" diff --git a/include/cpp-sort/sorters/cartesian_tree_sorter.h b/include/cpp-sort/sorters/cartesian_tree_sorter.h index ebc20035..bed31ba6 100644 --- a/include/cpp-sort/sorters/cartesian_tree_sorter.h +++ b/include/cpp-sort/sorters/cartesian_tree_sorter.h @@ -44,7 +44,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::forward_iterator_tag, + iterator_category, iterator_category_t >::value, "cartesian_tree_sorter requires at least forward iterators" @@ -69,7 +69,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::forward_iterator_tag, + iterator_category, iterator_category_t >::value, "cartesian_tree_sorter requires at least forward iterators" diff --git a/include/cpp-sort/sorters/counting_sorter.h b/include/cpp-sort/sorters/counting_sorter.h index d537e3b0..20608470 100644 --- a/include/cpp-sort/sorters/counting_sorter.h +++ b/include/cpp-sort/sorters/counting_sorter.h @@ -35,7 +35,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::forward_iterator_tag, + iterator_category, iterator_category_t >::value, "counting_sorter requires at least forward iterators" @@ -52,7 +52,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::forward_iterator_tag, + iterator_category, iterator_category_t >::value, "counting_sorter requires at least forward iterators" @@ -70,7 +70,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::forward_iterator_tag, + iterator_category, iterator_category_t >::value, "counting_sorter requires at least forward iterators" @@ -78,13 +78,13 @@ namespace cppsort reverse_counting_sort(std::move(first), std::move(last)); } +#endif //////////////////////////////////////////////////////////// // Sorter traits using iterator_category = std::forward_iterator_tag; using is_always_stable = std::false_type; -#endif }; } diff --git a/include/cpp-sort/sorters/drop_merge_sorter.h b/include/cpp-sort/sorters/drop_merge_sorter.h index aee36daf..ce3b3267 100644 --- a/include/cpp-sort/sorters/drop_merge_sorter.h +++ b/include/cpp-sort/sorters/drop_merge_sorter.h @@ -43,7 +43,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::bidirectional_iterator_tag, + iterator_category, iterator_category_t >::value, "drop_merge_sorter requires at least bidirectional iterators" diff --git a/include/cpp-sort/sorters/grail_sorter.h b/include/cpp-sort/sorters/grail_sorter.h index 4842bced..4da14947 100644 --- a/include/cpp-sort/sorters/grail_sorter.h +++ b/include/cpp-sort/sorters/grail_sorter.h @@ -44,7 +44,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::random_access_iterator_tag, + iterator_category, iterator_category_t >::value, "grail_sorter requires at least random-access iterators" diff --git a/include/cpp-sort/sorters/heap_sorter.h b/include/cpp-sort/sorters/heap_sorter.h index 783c8747..93127df7 100644 --- a/include/cpp-sort/sorters/heap_sorter.h +++ b/include/cpp-sort/sorters/heap_sorter.h @@ -43,7 +43,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::random_access_iterator_tag, + iterator_category, iterator_category_t >::value, "heap_sorter requires at least random-access iterators" diff --git a/include/cpp-sort/sorters/insertion_sorter.h b/include/cpp-sort/sorters/insertion_sorter.h index c5aa074d..9848e1d1 100644 --- a/include/cpp-sort/sorters/insertion_sorter.h +++ b/include/cpp-sort/sorters/insertion_sorter.h @@ -43,7 +43,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::bidirectional_iterator_tag, + iterator_category, iterator_category_t >::value, "insertion_sorter requires at least bidirectional iterators" diff --git a/include/cpp-sort/sorters/mel_sorter.h b/include/cpp-sort/sorters/mel_sorter.h index b52628e0..90c2820c 100644 --- a/include/cpp-sort/sorters/mel_sorter.h +++ b/include/cpp-sort/sorters/mel_sorter.h @@ -44,7 +44,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::forward_iterator_tag, + iterator_category, iterator_category_t >::value, "mel_sorter requires at least forward iterators" @@ -69,7 +69,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::forward_iterator_tag, + iterator_category, iterator_category_t >::value, "mel_sorter requires at least forward iterators" diff --git a/include/cpp-sort/sorters/merge_insertion_sorter.h b/include/cpp-sort/sorters/merge_insertion_sorter.h index fc7ddb99..7b720705 100644 --- a/include/cpp-sort/sorters/merge_insertion_sorter.h +++ b/include/cpp-sort/sorters/merge_insertion_sorter.h @@ -43,7 +43,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::random_access_iterator_tag, + iterator_category, iterator_category_t >::value, "merge_insertion_sorter requires at least random-access iterators" diff --git a/include/cpp-sort/sorters/merge_sorter.h b/include/cpp-sort/sorters/merge_sorter.h index 37f30ad3..f77a57b9 100644 --- a/include/cpp-sort/sorters/merge_sorter.h +++ b/include/cpp-sort/sorters/merge_sorter.h @@ -44,7 +44,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::forward_iterator_tag, + iterator_category, iterator_category_t >::value, "merge_sorter requires at least forward iterators" @@ -69,7 +69,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::forward_iterator_tag, + iterator_category, iterator_category_t >::value, "merge_sorter requires at least forward iterators" diff --git a/include/cpp-sort/sorters/pdq_sorter.h b/include/cpp-sort/sorters/pdq_sorter.h index 2a404af7..def4e7f1 100644 --- a/include/cpp-sort/sorters/pdq_sorter.h +++ b/include/cpp-sort/sorters/pdq_sorter.h @@ -43,7 +43,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::random_access_iterator_tag, + iterator_category, iterator_category_t >::value, "pdq_sorter requires at least random-access iterators" diff --git a/include/cpp-sort/sorters/poplar_sorter.h b/include/cpp-sort/sorters/poplar_sorter.h index bf6d4f6b..f98f0c06 100644 --- a/include/cpp-sort/sorters/poplar_sorter.h +++ b/include/cpp-sort/sorters/poplar_sorter.h @@ -43,7 +43,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::random_access_iterator_tag, + iterator_category, iterator_category_t >::value, "poplar_sorter requires at least random-access iterators" diff --git a/include/cpp-sort/sorters/quick_merge_sorter.h b/include/cpp-sort/sorters/quick_merge_sorter.h index 1314672b..0d1dd3db 100644 --- a/include/cpp-sort/sorters/quick_merge_sorter.h +++ b/include/cpp-sort/sorters/quick_merge_sorter.h @@ -44,7 +44,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::forward_iterator_tag, + iterator_category, iterator_category_t >::value, "quick_merge_sorter requires at least forward iterators" @@ -69,7 +69,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::forward_iterator_tag, + iterator_category, iterator_category_t >::value, "quick_merge_sorter requires at least forward iterators" diff --git a/include/cpp-sort/sorters/quick_sorter.h b/include/cpp-sort/sorters/quick_sorter.h index 16cebdc6..7fc2fa31 100644 --- a/include/cpp-sort/sorters/quick_sorter.h +++ b/include/cpp-sort/sorters/quick_sorter.h @@ -44,7 +44,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::forward_iterator_tag, + iterator_category, iterator_category_t >::value, "quick_sorter requires at least forward iterators" @@ -69,7 +69,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::forward_iterator_tag, + iterator_category, iterator_category_t >::value, "quick_sorter requires at least forward iterators" diff --git a/include/cpp-sort/sorters/selection_sorter.h b/include/cpp-sort/sorters/selection_sorter.h index 7aca171f..adeb96d9 100644 --- a/include/cpp-sort/sorters/selection_sorter.h +++ b/include/cpp-sort/sorters/selection_sorter.h @@ -43,7 +43,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::forward_iterator_tag, + iterator_category, iterator_category_t >::value, "selection_sorter requires at least forward iterators" diff --git a/include/cpp-sort/sorters/ska_sorter.h b/include/cpp-sort/sorters/ska_sorter.h index fa39e96d..48b6f953 100644 --- a/include/cpp-sort/sorters/ska_sorter.h +++ b/include/cpp-sort/sorters/ska_sorter.h @@ -44,7 +44,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::random_access_iterator_tag, + iterator_category, iterator_category_t >::value, "ska_sorter requires at least random-access iterators" diff --git a/include/cpp-sort/sorters/slab_sorter.h b/include/cpp-sort/sorters/slab_sorter.h index 760b03b7..8cf42604 100644 --- a/include/cpp-sort/sorters/slab_sorter.h +++ b/include/cpp-sort/sorters/slab_sorter.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include "../detail/iterator_traits.h" #include "../detail/slabsort.h" @@ -30,34 +31,59 @@ namespace cppsort struct slab_sorter_impl { template< - typename RandomAccessIterator, + typename BidirectionalIterable, typename Compare = std::less<>, typename Projection = utility::identity, typename = detail::enable_if_t< - is_projection_iterator_v + is_projection_v > > - auto operator()(RandomAccessIterator first, RandomAccessIterator last, + auto operator()(BidirectionalIterable&& iterable, Compare compare={}, Projection projection={}) const -> void { - // TODO: make it work for bidirectional iterators static_assert( std::is_base_of< - std::random_access_iterator_tag, - iterator_category_t + iterator_category, + iterator_category_t >::value, - "slab_sorter requires at least random-access iterators" + "slab_sorter requires at least bidirectional iterators" ); - slabsort(std::move(first), std::move(last), + slabsort(std::begin(iterable), std::end(iterable), + utility::size(iterable), + std::move(compare), std::move(projection)); + } + + template< + typename BidirectionalIterator, + typename Compare = std::less<>, + typename Projection = utility::identity, + typename = detail::enable_if_t< + is_projection_iterator_v + > + > + auto operator()(BidirectionalIterator first, BidirectionalIterator last, + Compare compare={}, Projection projection={}) const + -> void + { + static_assert( + std::is_base_of< + iterator_category, + iterator_category_t + >::value, + "slab_sorter requires at least bidirectional iterators" + ); + + auto size = std::distance(first, last); + slabsort(std::move(first), std::move(last), size, std::move(compare), std::move(projection)); } //////////////////////////////////////////////////////////// // Sorter traits - using iterator_category = std::random_access_iterator_tag; + using iterator_category = std::bidirectional_iterator_tag; using is_always_stable = std::false_type; }; } diff --git a/include/cpp-sort/sorters/smooth_sorter.h b/include/cpp-sort/sorters/smooth_sorter.h index 6afb4157..ad42ae63 100644 --- a/include/cpp-sort/sorters/smooth_sorter.h +++ b/include/cpp-sort/sorters/smooth_sorter.h @@ -43,7 +43,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::random_access_iterator_tag, + iterator_category, iterator_category_t >::value, "smooth_sorter requires at least random-access iterators" diff --git a/include/cpp-sort/sorters/spin_sorter.h b/include/cpp-sort/sorters/spin_sorter.h index 71b513e6..0f4232c1 100644 --- a/include/cpp-sort/sorters/spin_sorter.h +++ b/include/cpp-sort/sorters/spin_sorter.h @@ -43,7 +43,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::random_access_iterator_tag, + iterator_category, iterator_category_t >::value, "spin_sorter requires at least random-access iterators" diff --git a/include/cpp-sort/sorters/split_sorter.h b/include/cpp-sort/sorters/split_sorter.h index 6db5bc83..607e6072 100644 --- a/include/cpp-sort/sorters/split_sorter.h +++ b/include/cpp-sort/sorters/split_sorter.h @@ -43,7 +43,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::random_access_iterator_tag, + iterator_category, iterator_category_t >::value, "split_sorter requires at least random-access iterators" diff --git a/include/cpp-sort/sorters/spread_sorter/float_spread_sorter.h b/include/cpp-sort/sorters/spread_sorter/float_spread_sorter.h index 8a7b341d..11a7fa1d 100644 --- a/include/cpp-sort/sorters/spread_sorter/float_spread_sorter.h +++ b/include/cpp-sort/sorters/spread_sorter/float_spread_sorter.h @@ -46,7 +46,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::random_access_iterator_tag, + iterator_category, iterator_category_t >::value, "float_spread_sorter requires at least random-access iterators" diff --git a/include/cpp-sort/sorters/spread_sorter/integer_spread_sorter.h b/include/cpp-sort/sorters/spread_sorter/integer_spread_sorter.h index 9f5a3513..f48a0e25 100644 --- a/include/cpp-sort/sorters/spread_sorter/integer_spread_sorter.h +++ b/include/cpp-sort/sorters/spread_sorter/integer_spread_sorter.h @@ -46,7 +46,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::random_access_iterator_tag, + iterator_category, iterator_category_t >::value, "integer_spread_sorter requires at least random-access iterators" diff --git a/include/cpp-sort/sorters/spread_sorter/string_spread_sorter.h b/include/cpp-sort/sorters/spread_sorter/string_spread_sorter.h index 733df4a9..49273ea5 100644 --- a/include/cpp-sort/sorters/spread_sorter/string_spread_sorter.h +++ b/include/cpp-sort/sorters/spread_sorter/string_spread_sorter.h @@ -53,7 +53,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::random_access_iterator_tag, + iterator_category, iterator_category_t >::value, "string_spread_sorter requires at least random-access iterators" @@ -80,7 +80,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::random_access_iterator_tag, + iterator_category, iterator_category_t >::value, "string_spread_sorter requires at least random-access iterators" @@ -109,7 +109,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::random_access_iterator_tag, + iterator_category, iterator_category_t >::value, "string_spread_sorter requires at least random-access iterators" @@ -137,7 +137,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::random_access_iterator_tag, + iterator_category, iterator_category_t >::value, "string_spread_sorter requires at least random-access iterators" @@ -163,7 +163,7 @@ namespace cppsort { static_assert( std::is_base_of_v< - std::random_access_iterator_tag, + iterator_category, iterator_category_t >, "string_spread_sorter requires at least random-access iterators" @@ -189,7 +189,7 @@ namespace cppsort { static_assert( std::is_base_of_v< - std::random_access_iterator_tag, + iterator_category, iterator_category_t >, "string_spread_sorter requires at least random-access iterators" diff --git a/include/cpp-sort/sorters/std_sorter.h b/include/cpp-sort/sorters/std_sorter.h index 3b5797f0..c50abeb7 100644 --- a/include/cpp-sort/sorters/std_sorter.h +++ b/include/cpp-sort/sorters/std_sorter.h @@ -43,7 +43,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::random_access_iterator_tag, + iterator_category, iterator_category_t >::value, "std_sorter requires at least random-access iterators" diff --git a/include/cpp-sort/sorters/tim_sorter.h b/include/cpp-sort/sorters/tim_sorter.h index 1afbeddb..e1488fb2 100644 --- a/include/cpp-sort/sorters/tim_sorter.h +++ b/include/cpp-sort/sorters/tim_sorter.h @@ -43,7 +43,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::random_access_iterator_tag, + iterator_category, iterator_category_t >::value, "tim_sorter requires at least random-access iterators" diff --git a/include/cpp-sort/sorters/verge_sorter.h b/include/cpp-sort/sorters/verge_sorter.h index 94bce6d3..e7c2b7f8 100644 --- a/include/cpp-sort/sorters/verge_sorter.h +++ b/include/cpp-sort/sorters/verge_sorter.h @@ -47,7 +47,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::bidirectional_iterator_tag, + iterator_category, iterator_category_t >::value, "verge_sorter requires at least bidirectional iterators" @@ -72,7 +72,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::bidirectional_iterator_tag, + iterator_category, iterator_category_t >::value, "verge_sorter requires at least bidirectional iterators" diff --git a/include/cpp-sort/sorters/wiki_sorter.h b/include/cpp-sort/sorters/wiki_sorter.h index b70f4d79..9b24045f 100644 --- a/include/cpp-sort/sorters/wiki_sorter.h +++ b/include/cpp-sort/sorters/wiki_sorter.h @@ -45,7 +45,7 @@ namespace cppsort { static_assert( std::is_base_of< - std::random_access_iterator_tag, + iterator_category, iterator_category_t >::value, "wiki_sorter requires at least random-access iterators" diff --git a/include/cpp-sort/utility/size.h b/include/cpp-sort/utility/size.h index 9f40460e..8daaa6d2 100644 --- a/include/cpp-sort/utility/size.h +++ b/include/cpp-sort/utility/size.h @@ -48,6 +48,18 @@ namespace utility return std::distance(std::begin(iterable), std::end(iterable)); } + template< + typename Iterable, + typename = cppsort::detail::enable_if_t< + not cppsort::detail::is_detected_v + > + > + constexpr auto size(Iterable& iterable) + -> decltype(std::distance(std::begin(iterable), std::end(iterable))) + { + return std::distance(std::begin(iterable), std::end(iterable)); + } + template constexpr auto size(const T (&)[N]) noexcept -> std::size_t diff --git a/include/cpp-sort/version.h b/include/cpp-sort/version.h index f58675ad..234660a8 100644 --- a/include/cpp-sort/version.h +++ b/include/cpp-sort/version.h @@ -9,6 +9,6 @@ #define CPPSORT_VERSION_MAJOR 1 #define CPPSORT_VERSION_MINOR 12 -#define CPPSORT_VERSION_PATCH 0 +#define CPPSORT_VERSION_PATCH 1 #endif // CPPSORT_VERSION_H_ diff --git a/testsuite/CMakeLists.txt b/testsuite/CMakeLists.txt index da04bff0..87eaee9c 100644 --- a/testsuite/CMakeLists.txt +++ b/testsuite/CMakeLists.txt @@ -120,6 +120,7 @@ add_executable(main-tests every_sorter_small_collections.cpp every_sorter_span.cpp every_sorter_throwing_moves.cpp + every_sorter_tricky_difference_type.cpp is_stable.cpp rebind_iterator_category.cpp sort_array.cpp @@ -139,6 +140,7 @@ add_executable(main-tests adapters/every_adapter_internal_compare.cpp adapters/every_adapter_non_const_compare.cpp adapters/every_adapter_stateful_sorter.cpp + adapters/every_adapter_tricky_difference_type.cpp adapters/hybrid_adapter_is_stable.cpp adapters/hybrid_adapter_many_sorters.cpp adapters/hybrid_adapter_nested.cpp diff --git a/testsuite/adapters/every_adapter_tricky_difference_type.cpp b/testsuite/adapters/every_adapter_tricky_difference_type.cpp new file mode 100644 index 00000000..33c44d47 --- /dev/null +++ b/testsuite/adapters/every_adapter_tricky_difference_type.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2021 Morwenn + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include +#include + +TEMPLATE_TEST_CASE( "test adapters with an int8_t difference_type", "[adapters]", + cppsort::indirect_adapter, + cppsort::out_of_place_adapter, + cppsort::schwartz_adapter, + cppsort::stable_adapter, + cppsort::verge_adapter + ) +{ + // Test that adapters work as expected when the iterator to sort has + // a small difference_type, despite potential promotions to int + + test_vector collection(127); + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(collection), 127); + + TestType sorter; + sorter(collection); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); +} diff --git a/testsuite/adapters/indirect_adapter_every_sorter.cpp b/testsuite/adapters/indirect_adapter_every_sorter.cpp index 2dc44246..7c9c93b7 100644 --- a/testsuite/adapters/indirect_adapter_every_sorter.cpp +++ b/testsuite/adapters/indirect_adapter_every_sorter.cpp @@ -57,6 +57,7 @@ TEMPLATE_TEST_CASE( "every bidirectional sorter with indirect_adapter", "[indire cppsort::quick_merge_sorter, cppsort::quick_sorter, cppsort::selection_sorter, + cppsort::slab_sorter, cppsort::verge_sorter ) { std::list collection; diff --git a/testsuite/adapters/schwartz_adapter_every_sorter.cpp b/testsuite/adapters/schwartz_adapter_every_sorter.cpp index 8ec303f2..b46118cf 100644 --- a/testsuite/adapters/schwartz_adapter_every_sorter.cpp +++ b/testsuite/adapters/schwartz_adapter_every_sorter.cpp @@ -65,6 +65,7 @@ TEMPLATE_TEST_CASE( "every bidirectional sorter with Schwartzian transform adapt cppsort::quick_merge_sorter, cppsort::quick_sorter, cppsort::selection_sorter, + cppsort::slab_sorter, cppsort::verge_sorter ) { std::list> collection; diff --git a/testsuite/adapters/stable_adapter_every_sorter.cpp b/testsuite/adapters/stable_adapter_every_sorter.cpp index b23f2519..ff8625c9 100644 --- a/testsuite/adapters/stable_adapter_every_sorter.cpp +++ b/testsuite/adapters/stable_adapter_every_sorter.cpp @@ -80,6 +80,7 @@ TEMPLATE_TEST_CASE( "every bidirectional sorter with stable_adapter", "[stable_a cppsort::quick_merge_sorter, cppsort::quick_sorter, cppsort::selection_sorter, + cppsort::slab_sorter, cppsort::verge_sorter ) { cppsort::stable_t sorter; diff --git a/testsuite/distributions/ascending_sawtooth.cpp b/testsuite/distributions/ascending_sawtooth.cpp index 5388814b..08363da1 100644 --- a/testsuite/distributions/ascending_sawtooth.cpp +++ b/testsuite/distributions/ascending_sawtooth.cpp @@ -56,6 +56,7 @@ TEMPLATE_TEST_CASE( "test bidirectional sorters with ascending_sawtooth distribu cppsort::merge_sorter, cppsort::quick_merge_sorter, cppsort::quick_sorter, + cppsort::slab_sorter, cppsort::verge_sorter ) { std::list collection; diff --git a/testsuite/distributions/descending_sawtooth.cpp b/testsuite/distributions/descending_sawtooth.cpp index a636f0dc..b8c4abf2 100644 --- a/testsuite/distributions/descending_sawtooth.cpp +++ b/testsuite/distributions/descending_sawtooth.cpp @@ -56,6 +56,7 @@ TEMPLATE_TEST_CASE( "test bidirectional sorters with descending_sawtooth distrib cppsort::merge_sorter, cppsort::quick_merge_sorter, cppsort::quick_sorter, + cppsort::slab_sorter, cppsort::verge_sorter ) { std::list collection; diff --git a/testsuite/distributions/median_of_3_killer.cpp b/testsuite/distributions/median_of_3_killer.cpp index 01e7e8d6..73e690fa 100644 --- a/testsuite/distributions/median_of_3_killer.cpp +++ b/testsuite/distributions/median_of_3_killer.cpp @@ -55,6 +55,7 @@ TEMPLATE_TEST_CASE( "test bidirectional sorters with median_of_3_killer distribu cppsort::merge_sorter, cppsort::quick_merge_sorter, cppsort::quick_sorter, + cppsort::slab_sorter, cppsort::verge_sorter ) { std::list collection; diff --git a/testsuite/every_sorter.cpp b/testsuite/every_sorter.cpp index 088ce56e..a45a5231 100644 --- a/testsuite/every_sorter.cpp +++ b/testsuite/every_sorter.cpp @@ -83,18 +83,16 @@ TEMPLATE_TEST_CASE( "test every bidirectional sorter", "[sorters]", cppsort::quick_merge_sorter, cppsort::quick_sorter, cppsort::selection_sorter, + cppsort::slab_sorter, cppsort::verge_sorter ) { - SECTION( "with std::list" ) - { - std::list collection; - auto distribution = dist::shuffled{}; - distribution(std::back_inserter(collection), 491, -125); + std::list collection; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(collection), 491, -125); - TestType sorter; - sorter(collection); - CHECK( std::is_sorted(collection.begin(), collection.end()) ); - } + TestType sorter; + sorter(collection); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); } TEMPLATE_TEST_CASE( "test every forward sorter", "[sorters]", @@ -106,14 +104,11 @@ TEMPLATE_TEST_CASE( "test every forward sorter", "[sorters]", cppsort::quick_sorter, cppsort::selection_sorter ) { - SECTION( "wwith std::forward_list" ) - { - std::forward_list collection; - auto distribution = dist::shuffled{}; - distribution(std::front_inserter(collection), 491, -125); + std::forward_list collection; + auto distribution = dist::shuffled{}; + distribution(std::front_inserter(collection), 491, -125); - TestType sorter; - sorter(collection); - CHECK( std::is_sorted(collection.begin(), collection.end()) ); - } + TestType sorter; + sorter(collection); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); } diff --git a/testsuite/every_sorter_rvalue_projection.cpp b/testsuite/every_sorter_rvalue_projection.cpp index 91194582..92ad4a11 100644 --- a/testsuite/every_sorter_rvalue_projection.cpp +++ b/testsuite/every_sorter_rvalue_projection.cpp @@ -73,18 +73,16 @@ TEMPLATE_TEST_CASE( "bidirectional sorters with a projection returning an rvalue cppsort::quick_merge_sorter, cppsort::quick_sorter, cppsort::selection_sorter, + cppsort::slab_sorter, cppsort::verge_sorter ) { - SECTION( "std::list" ) - { - std::list collection; - auto distribution = dist::shuffled{}; - distribution(std::back_inserter(collection), 50); + std::list collection; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(collection), 50); - TestType sorter; - sorter(collection, std::negate<>{}); - CHECK( std::is_sorted(collection.begin(), collection.end(), std::greater<>{}) ); - } + TestType sorter; + sorter(collection, std::negate<>{}); + CHECK( std::is_sorted(collection.begin(), collection.end(), std::greater<>{}) ); } TEMPLATE_TEST_CASE( "forward sorters with a projection returning an rvalue", "[sorters][projection]", @@ -95,14 +93,11 @@ TEMPLATE_TEST_CASE( "forward sorters with a projection returning an rvalue", "[s cppsort::quick_sorter, cppsort::selection_sorter ) { - SECTION( "std::forward_list" ) - { - std::forward_list collection; - auto distribution = dist::shuffled{}; - distribution(std::front_inserter(collection), 50); + std::forward_list collection; + auto distribution = dist::shuffled{}; + distribution(std::front_inserter(collection), 50); - TestType sorter; - sorter(collection, std::negate<>{}); - CHECK( std::is_sorted(collection.begin(), collection.end(), std::greater<>{}) ); - } + TestType sorter; + sorter(collection, std::negate<>{}); + CHECK( std::is_sorted(collection.begin(), collection.end(), std::greater<>{}) ); } diff --git a/testsuite/every_sorter_throwing_moves.cpp b/testsuite/every_sorter_throwing_moves.cpp index c33fec37..9bd0dc59 100644 --- a/testsuite/every_sorter_throwing_moves.cpp +++ b/testsuite/every_sorter_throwing_moves.cpp @@ -153,6 +153,7 @@ TEMPLATE_TEST_CASE( "bidirectional sorters against throwing move operations", "[ cppsort::quick_merge_sorter, cppsort::quick_sorter, cppsort::selection_sorter, + cppsort::slab_sorter, cppsort::verge_sorter ) { auto distribution = dist::shuffled{}; @@ -161,20 +162,17 @@ TEMPLATE_TEST_CASE( "bidirectional sorters against throwing move operations", "[ correctly_destructed = 0; moves_count = 0; - SECTION( "with std::list" ) - { - try { - std::list collection; - distribution(std::back_inserter(collection), 250); + try { + std::list collection; + distribution(std::back_inserter(collection), 250); - TestType sorter; - sorter(collection); + TestType sorter; + sorter(collection); - INFO( "the sorter did not throw" ); - } catch (const throw_on_move_error&) { - INFO( "the sorter did throw" ); - CHECK( correctly_constructed == correctly_destructed ); - } + INFO( "the sorter did not throw" ); + } catch (const throw_on_move_error&) { + INFO( "the sorter did throw" ); + CHECK( correctly_constructed == correctly_destructed ); } } @@ -192,19 +190,16 @@ TEMPLATE_TEST_CASE( "forward sorters against throwing move operations", "[sorter correctly_destructed = 0; moves_count = 0; - SECTION( "with std::forward_list" ) - { - try { - std::forward_list collection; - distribution(std::front_inserter(collection), 250); + try { + std::forward_list collection; + distribution(std::front_inserter(collection), 250); - TestType sorter; - sorter(collection); + TestType sorter; + sorter(collection); - INFO( "the sorter did not throw" ); - } catch (const throw_on_move_error&) { - INFO( "the sorter did throw" ); - CHECK( correctly_constructed == correctly_destructed ); - } + INFO( "the sorter did not throw" ); + } catch (const throw_on_move_error&) { + INFO( "the sorter did throw" ); + CHECK( correctly_constructed == correctly_destructed ); } } diff --git a/testsuite/every_sorter_tricky_difference_type.cpp b/testsuite/every_sorter_tricky_difference_type.cpp new file mode 100644 index 00000000..283ade84 --- /dev/null +++ b/testsuite/every_sorter_tricky_difference_type.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2021 Morwenn + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +TEMPLATE_TEST_CASE( "test every sorter with an int8_t difference_type", "[sorters]", + cppsort::cartesian_tree_sorter, + cppsort::counting_sorter, + cppsort::drop_merge_sorter, + cppsort::grail_sorter<>, + cppsort::grail_sorter< + cppsort::utility::dynamic_buffer + >, + cppsort::heap_sorter, + cppsort::insertion_sorter, + cppsort::mel_sorter, + cppsort::merge_insertion_sorter, + cppsort::merge_sorter, + cppsort::pdq_sorter, + cppsort::poplar_sorter, + cppsort::quick_merge_sorter, + cppsort::quick_sorter, + cppsort::selection_sorter, + cppsort::ska_sorter, + cppsort::slab_sorter, + cppsort::smooth_sorter, + cppsort::spin_sorter, + cppsort::split_sorter, + cppsort::std_sorter, + cppsort::tim_sorter, + cppsort::verge_sorter, + cppsort::wiki_sorter<>, + cppsort::wiki_sorter< + cppsort::utility::dynamic_buffer + > ) +{ + // Test that sorters work as expected when the iterator to sort has + // a small difference_type, despite potential promotions to int + + test_vector collection(127); + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(collection), 127); + + TestType sorter; + sorter(collection); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); +} diff --git a/testsuite/testing-tools/no_post_iterator.h b/testsuite/testing-tools/no_post_iterator.h index 4f8bd268..78ac4c3e 100644 --- a/testsuite/testing-tools/no_post_iterator.h +++ b/testsuite/testing-tools/no_post_iterator.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2017 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_TESTSUITE_NO_POST_ITERATOR_H_ @@ -55,6 +55,7 @@ class no_post_iterator //////////////////////////////////////////////////////////// // Members access + CPPSORT_ATTRIBUTE_NODISCARD auto base() const -> iterator_type { @@ -64,12 +65,14 @@ class no_post_iterator //////////////////////////////////////////////////////////// // Element access + CPPSORT_ATTRIBUTE_NODISCARD auto operator*() const -> decltype(*base()) { - return *base(); + return *_it; } + CPPSORT_ATTRIBUTE_NODISCARD auto operator->() const -> pointer { @@ -110,118 +113,109 @@ class no_post_iterator //////////////////////////////////////////////////////////// // Elements access operators - auto operator[](difference_type pos) + CPPSORT_ATTRIBUTE_NODISCARD + auto operator[](difference_type pos) const -> decltype(base()[pos]) { - return base()[pos]; + return _it[pos]; } - auto operator[](difference_type pos) const - -> decltype(base()[pos]) + //////////////////////////////////////////////////////////// + // Comparison operators + + CPPSORT_ATTRIBUTE_NODISCARD + friend auto operator==(const no_post_iterator& lhs, const no_post_iterator& rhs) + -> bool { - return base()[pos]; + return lhs.base() == rhs.base(); } - private: - - Iterator _it; -}; + CPPSORT_ATTRIBUTE_NODISCARD + friend auto operator!=(const no_post_iterator& lhs, const no_post_iterator& rhs) + -> bool + { + return lhs.base() != rhs.base(); + } -template -auto iter_swap(no_post_iterator lhs, no_post_iterator rhs) - -> void -{ - using cppsort::utility::iter_swap; - iter_swap(lhs.base(), rhs.base()); -} + //////////////////////////////////////////////////////////// + // Relational operators -//////////////////////////////////////////////////////////// -// Comparison operators + CPPSORT_ATTRIBUTE_NODISCARD + friend auto operator<(const no_post_iterator& lhs, const no_post_iterator& rhs) + -> bool + { + return lhs.base() < rhs.base(); + } -template -auto operator==(const no_post_iterator& lhs, - const no_post_iterator& rhs) - -> bool -{ - return lhs.base() == rhs.base(); -} + CPPSORT_ATTRIBUTE_NODISCARD + friend auto operator<=(const no_post_iterator& lhs, const no_post_iterator& rhs) + -> bool + { + return lhs.base() <= rhs.base(); + } -template -auto operator!=(const no_post_iterator& lhs, - const no_post_iterator& rhs) - -> bool -{ - return lhs.base() != rhs.base(); -} + CPPSORT_ATTRIBUTE_NODISCARD + friend auto operator>(const no_post_iterator& lhs, const no_post_iterator& rhs) + -> bool + { + return lhs.base() > rhs.base(); + } -//////////////////////////////////////////////////////////// -// Relational operators + CPPSORT_ATTRIBUTE_NODISCARD + friend auto operator>=(const no_post_iterator& lhs, const no_post_iterator& rhs) + -> bool + { + return lhs.base() >= rhs.base(); + } -template -auto operator<(const no_post_iterator& lhs, - const no_post_iterator& rhs) - -> bool -{ - return lhs.base() < rhs.base(); -} + //////////////////////////////////////////////////////////// + // Arithmetic operators -template -auto operator<=(const no_post_iterator& lhs, - const no_post_iterator& rhs) - -> bool -{ - return lhs.base() <= rhs.base(); -} + CPPSORT_ATTRIBUTE_NODISCARD + friend auto operator+(no_post_iterator it, difference_type size) + -> no_post_iterator + { + it += size; + return it; + } -template -auto operator>(const no_post_iterator& lhs, - const no_post_iterator& rhs) - -> bool -{ - return lhs.base() > rhs.base(); -} + CPPSORT_ATTRIBUTE_NODISCARD + friend auto operator+(difference_type size, no_post_iterator it) + -> no_post_iterator + { + it += size; + return it; + } -template -auto operator>=(const no_post_iterator& lhs, - const no_post_iterator& rhs) - -> bool -{ - return lhs.base() >= rhs.base(); -} + CPPSORT_ATTRIBUTE_NODISCARD + friend auto operator-(no_post_iterator it, difference_type size) + -> no_post_iterator + { + it -= size; + return it; + } -//////////////////////////////////////////////////////////// -// Arithmetic operators + CPPSORT_ATTRIBUTE_NODISCARD + friend auto operator-(const no_post_iterator& lhs, const no_post_iterator& rhs) + -> difference_type + { + return lhs.base() - rhs.base(); + } -template -auto operator+(no_post_iterator it, - cppsort::detail::difference_type_t> size) - -> no_post_iterator -{ - return it += size; -} + //////////////////////////////////////////////////////////// + // iter_swap -template -auto operator+(cppsort::detail::difference_type_t> size, - no_post_iterator it) - -> no_post_iterator -{ - return it += size; -} + friend auto iter_swap(no_post_iterator lhs, no_post_iterator rhs) + -> void + { + using cppsort::utility::iter_swap; + iter_swap(lhs.base(), rhs.base()); + } -template -auto operator-(no_post_iterator it, - cppsort::detail::difference_type_t> size) - -> no_post_iterator -{ - return it -= size; -} + private: -template -auto operator-(const no_post_iterator& lhs, const no_post_iterator& rhs) - -> cppsort::detail::difference_type_t> -{ - return lhs.base() - rhs.base(); -} + Iterator _it; +}; //////////////////////////////////////////////////////////// // Construction function diff --git a/testsuite/testing-tools/test_vector.h b/testsuite/testing-tools/test_vector.h new file mode 100644 index 00000000..036f0288 --- /dev/null +++ b/testsuite/testing-tools/test_vector.h @@ -0,0 +1,364 @@ +/* + * Copyright (c) 2021 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_TESTSUITE_TEST_VECTOR_H_ +#define CPPSORT_TESTSUITE_TEST_VECTOR_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include + +//////////////////////////////////////////////////////////// +// Vector with a tiny size +// +// Customizable vector type for testing purposes +// + +struct test_vector_error: + std::runtime_error +{ + using std::runtime_error::runtime_error; +}; + +template +class test_vector; + +template +class test_vector_iterator +{ + public: + + //////////////////////////////////////////////////////////// + // Public types + + using iterator_category = std::random_access_iterator_tag; + using value_type = T; + using difference_type = DifferenceType; + using pointer = value_type*; + using reference = value_type&; + + //////////////////////////////////////////////////////////// + // Constructors & operator= + + test_vector_iterator() = default; + test_vector_iterator(const test_vector_iterator&) = default; + test_vector_iterator(test_vector_iterator&&) = default; + test_vector_iterator& operator=(const test_vector_iterator&) = default; + test_vector_iterator& operator=(test_vector_iterator&&) = default; + + constexpr explicit test_vector_iterator(T* ptr, test_vector* owner): + ptr_(ptr), + owner_(owner) + {} + + //////////////////////////////////////////////////////////// + // Element access + + auto operator*() const + -> reference + { + return *ptr_; + } + + auto operator->() const + -> pointer + { + return &(operator*()); + } + + //////////////////////////////////////////////////////////// + // Increment/decrement operators + + auto operator++() + -> test_vector_iterator& + { + if (ptr_ == owner_->end_) { + throw test_vector_error("operator++ creates out-of-bounds iterator"); + } + ++ptr_; + return *this; + } + + auto operator++(int) + -> test_vector_iterator + { + auto tmp = *this; + operator++(); + return tmp; + } + + auto operator--() + -> test_vector_iterator& + { + if (ptr_ == owner_->memory_) { + throw test_vector_error("operator-- creates out-of-bounds iterator"); + } + --ptr_; + return *this; + } + + auto operator--(int) + -> test_vector_iterator + { + auto tmp = *this; + operator--(); + return tmp; + } + + auto operator+=(difference_type increment) + -> test_vector_iterator& + { + if (ptr_ + increment > owner_->end_) { + throw test_vector_error("operator+= creates out-of-bounds iterator (end)"); + } + if (ptr_ + increment < owner_->memory_) { + throw test_vector_error("operator+= creates out-of-bounds iterator (begin)"); + } + ptr_ += increment; + return *this; + } + + auto operator-=(difference_type increment) + -> test_vector_iterator& + { + if (ptr_ - increment < owner_->memory_) { + throw test_vector_error("operator-= creates out-of-bounds iterator (begin)"); + } + if (ptr_ - increment > owner_->end_) { + throw test_vector_error("operator-= creates out-of-bounds iterator (end)"); + } + ptr_ -= increment; + return *this; + } + + //////////////////////////////////////////////////////////// + // Elements access operators + + auto operator[](difference_type pos) const + -> reference + { + if (ptr_ + pos > owner_->end_) { + throw test_vector_error("operator[] creates out-of-bounds iterator (end)"); + } + if (ptr_ + pos < owner_->memory_) { + throw test_vector_error("operator[] creates out-of-bounds iterator (begin)"); + } + return ptr_[pos]; + } + + //////////////////////////////////////////////////////////// + // Comparison operators + + friend constexpr auto operator==(const test_vector_iterator& lhs, const test_vector_iterator& rhs) + -> bool + { + return lhs.ptr_ == rhs.ptr_; + } + + friend constexpr auto operator!=(const test_vector_iterator& lhs, const test_vector_iterator& rhs) + -> bool + { + return lhs.ptr_ != rhs.ptr_; + } + + //////////////////////////////////////////////////////////// + // Relational operators + + friend auto operator<(const test_vector_iterator& lhs, const test_vector_iterator& rhs) + -> bool + { + return lhs.ptr_ < rhs.ptr_; + } + + friend auto operator<=(const test_vector_iterator& lhs, const test_vector_iterator& rhs) + -> bool + { + return lhs.ptr_ <= rhs.ptr_; + } + + friend auto operator>(const test_vector_iterator& lhs, const test_vector_iterator& rhs) + -> bool + { + return lhs.ptr_ > rhs.ptr_; + } + + friend auto operator>=(const test_vector_iterator& lhs, const test_vector_iterator& rhs) + -> bool + { + return lhs.ptr_ >= rhs.ptr_; + } + + //////////////////////////////////////////////////////////// + // Arithmetic operators + + friend auto operator+(test_vector_iterator it, difference_type size) + -> test_vector_iterator + { + it += size; + return it; + } + + friend auto operator+(difference_type size, test_vector_iterator it) + -> test_vector_iterator + { + it += size; + return it; + } + + friend auto operator-(test_vector_iterator it, difference_type size) + -> test_vector_iterator + { + it -= size; + return it; + } + + friend auto operator-(const test_vector_iterator& lhs, const test_vector_iterator& rhs) + -> difference_type + { + return lhs.ptr_ - rhs.ptr_; + } + + private: + + T* ptr_ = nullptr; + test_vector* owner_ = nullptr; +}; + +template +class test_vector +{ + friend class test_vector_iterator; + + public: + + //////////////////////////////////////////////////////////// + // Member types + + using value_type = T; + using difference_type = DifferenceType; + using reference = value_type&; + using const_reference = const value_type&; + using iterator = test_vector_iterator; + + //////////////////////////////////////////////////////////// + // Make the collection immovable + + test_vector() = default; + test_vector(const test_vector&) = delete; + test_vector(test_vector&&) = delete; + test_vector& operator=(const test_vector&) = delete; + test_vector& operator=(test_vector&&) = delete; + + //////////////////////////////////////////////////////////// + // Construction + + explicit test_vector(difference_type n): + capacity_(n), + memory_( + static_cast(::operator new(n * sizeof(T))) + ), + end_(memory_) + {} + + //////////////////////////////////////////////////////////// + // Destruction + + ~test_vector() + { + // Destroy the constructed elements + cppsort::detail::destroy(memory_, end_); + // Free the allocated memory + ::operator delete(memory_); + } + + //////////////////////////////////////////////////////////// + // Element access + + auto operator[](difference_type pos) + -> T& + { + if (pos < 0) { + throw test_vector_error("out-of-bounds access in operator[]"); + } + return memory_[pos]; + } + + auto front() + -> T& + { + if (memory_ == end_) { + throw test_vector_error("front() called on empty collection"); + } + return *memory_; + } + + auto back() + -> T& + { + if (memory_ == end_) { + throw test_vector_error("back() called on empty collection"); + } + return *(end_ - 1); + } + + //////////////////////////////////////////////////////////// + // Iterators + + auto begin() + -> iterator + { + return iterator(memory_, this); + } + + auto end() + -> iterator + { + return iterator(end_, this); + } + + //////////////////////////////////////////////////////////// + // Modifiers + + auto clear() noexcept + -> void + { + // Destroy the constructed elements + cppsort::detail::destroy(memory_, end_); + // Ensure the new size is zero + end_ = memory_; + } + + auto push_back(T& value) + -> void + { + emplace_back(value); + } + + auto push_back(T&& value) + -> void + { + emplace_back(std::move(value)); + } + + template + auto emplace_back(Args&&... args) + -> void + { + if (end_ - memory_ >= capacity_) { + throw test_vector_error("capacity reached when calling emplace_back()"); + } + ::new(end_) T(std::forward(args)...); + ++end_; + } + + private: + + std::ptrdiff_t capacity_ = 0; + T* memory_ = nullptr; + T* end_ = nullptr; +}; + +#endif // CPPSORT_TESTSUITE_TEST_VECTOR_H_ diff --git a/tools/release-checklist.md b/tools/release-checklist.md index f249ddae..64808bb2 100644 --- a/tools/release-checklist.md +++ b/tools/release-checklist.md @@ -2,10 +2,9 @@ List of actions to perform when releasing a new cpp-sort version. ### During the development -The following things need be done prior to the release if they didn't happen during the main -development phase: - [ ] Update the documentation. - [ ] Update the releases notes. +- [ ] Udate `NOTICE.txt` and `README.md` when stealing code. - [ ] Keep track of the things that will be changed in 2.0.0. ### Before the release @@ -14,15 +13,15 @@ development phase: - [ ] Check that all issues linked to the milestone are closed. - [ ] Check `NOTICE.txt` and `README.md` conformance for stolen code. - [ ] Make sure that tests pass and examples build. -- [ ] Regenerate the benchmarks. -- [ ] Replace occurences of the version number: - - [ ] CMakeLists.txt - - [ ] conanfile.py - - [ ] README.md +- [ ] Regenerate the benchmarks as needed. +- [ ] Replace occurrences of the version number: + - [ ] CMakeLists.txt (1) + - [ ] conanfile.py (1) + - [ ] README.md (4) - [ ] version.h - - [ ] Home.md in the documentation - - [ ] Tooling.md/Conan in the documentation (2 mentions) -- [ ] Make sure that the Conan recipe works. + - [ ] Home.md in the documentation (1) + - [ ] Tooling.md/Conan in the documentation (2) +- [ ] Verify that the Conan recipe works. - [ ] Find a name for the new version. - [ ] Open a merge request, let the CI do its job. - [ ] Merge `develop` into `master`. @@ -32,6 +31,7 @@ development phase: - [ ] Add the Zenodo badge to the release notes. - [ ] Close the new version's milestone. +- [ ] Check that the documentation was correctly uploaded. - [ ] Add the new version to Conan Center Index. - [ ] Brag about it where relevant. - [ ] Merge master into 2.0.0-develop branch.