From ae7f1667830dd2b7fbab48b6e83715022e2d1dd8 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 29 Oct 2020 17:44:24 +0100 Subject: [PATCH 01/90] Reduce .gitignore to files generated by the project's tooling [ci skip] --- .gitignore | 44 ++++---------------------------------------- 1 file changed, 4 insertions(+), 40 deletions(-) diff --git a/.gitignore b/.gitignore index 0157b8fe..d82f1cc6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,46 +1,10 @@ # Copyright (c) 2015-2020 Morwenn # SPDX-License-Identifier: MIT -# Project-specific directory +# Usual build directory build -# Compiled Object files -*.slo -*.lo -*.o -*.obj - -# Compiled Dynamic libraries -*.so -*.dylib -*.dll - -# Compiled Static libraries -*.lai -*.la -*.a -*.lib - -# Executables -*.exe -*.out -*.app - -# Code::Blocks files -*.cbp -*.depend -*.layout -*.save-failed - -# VSCode files -.vscode - -# Static analyzer files -CppCheckResults.xml - -# Files generated by LaTeX -*.aux -*.log +# Files generated by project scripts +*.csv *.png -*.pdf -*.synctex.gz +tools/*.txt From b445ec4365e10c45dcbfe88f8fdda19fb9173387 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 1 Nov 2020 17:33:48 +0100 Subject: [PATCH 02/90] List long-missed tests in CMakeLists.txt --- testsuite/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/testsuite/CMakeLists.txt b/testsuite/CMakeLists.txt index eda5d3e5..7a900db3 100644 --- a/testsuite/CMakeLists.txt +++ b/testsuite/CMakeLists.txt @@ -99,9 +99,11 @@ add_executable(main-tests every_sorter_span.cpp is_stable.cpp rebind_iterator_category.cpp + sort_array.cpp sorter_facade.cpp sorter_facade_defaults.cpp sorter_facade_iterable.cpp + stable_sort_array.cpp # Adapters tests adapters/container_aware_adapter.cpp From 65d43bc80e5c4afe090b9c4483e5fdccbc1b1cd3 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 1 Nov 2020 19:37:10 +0100 Subject: [PATCH 03/90] Reduce use of cppsort::[stable_]sort even more This also fixes an issue in the "writing a sorter" tutorial that used the pre-1.0.0 arguments order for cppsort::sort. --- docs/Library-nomenclature.md | 4 +- docs/Writing-a-bubble_sorter.md | 2 +- docs/Writing-a-sorter.md | 4 +- .../adapters/container_aware_adapter.h | 2 +- testsuite/utility/as_projection.cpp | 56 ++++++++++--------- testsuite/utility/as_projection_iterable.cpp | 50 ++++++++--------- 6 files changed, 58 insertions(+), 60 deletions(-) diff --git a/docs/Library-nomenclature.md b/docs/Library-nomenclature.md index fcca86ea..c162e82d 100644 --- a/docs/Library-nomenclature.md +++ b/docs/Library-nomenclature.md @@ -8,7 +8,7 @@ * *Comparison function*: most of the sorting algorithms in the library are comparison sorts. It means that the algorithm uses a comparison function to know the order of the elements and sort them accordingly; such a comparison function shall take two values and have a return type convertible to `bool`. The available sorting algorithms transform comparison functions on the fly so that some pointers to member functions can also be used as comparison functions, as if called with [`std::invoke`](http://en.cppreference.com/w/cpp/utility/functional/invoke). The default comparison function used by the sorting algorithms is [`std::less<>`](http://en.cppreference.com/w/cpp/utility/functional/less_void). Many sorters can take a comparison function as an additional parameter. For example, using `std::greater<>` instead of the default comparison function would sort a collection in descending order. - cppsort::sort(collection, std::greater<>{}); + cppsort::heap_sort(collection, std::greater<>{}); Some algorithms don't accept such an additional parameter. It may be because they implement a non-comparison sort instead, a sorting algorithm that uses other properties of the elements to perform the sort rather than a comparison function (for example a [radix sort](https://en.wikipedia.org/wiki/Radix_sort)). @@ -30,7 +30,7 @@ struct wrapper { int value; }; std::vector collection = { /* ... */ }; - cppsort::sort(collection, &wrapper::value); + cppsort::heap_sort(collection, &wrapper::value); Every *comparison sorter* is also a *projection sorter*, but there are also projection-only sorters, such as [`spread_sorter`](https://github.com/Morwenn/cpp-sort/wiki/Sorters). diff --git a/docs/Writing-a-bubble_sorter.md b/docs/Writing-a-bubble_sorter.md index 833f2859..38528ebf 100644 --- a/docs/Writing-a-bubble_sorter.md +++ b/docs/Writing-a-bubble_sorter.md @@ -261,7 +261,7 @@ 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. -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 `cppsort::sort` won't call the functor 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 (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). 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: diff --git a/docs/Writing-a-sorter.md b/docs/Writing-a-sorter.md index a24864e0..3f68700e 100644 --- a/docs/Writing-a-sorter.md +++ b/docs/Writing-a-sorter.md @@ -153,7 +153,7 @@ This kind of comparison sorters help to compare things that don't have an overlo ```cpp // Sort collection in reverse order with std::sort -cppsort::sort(collection, std_sorter{}, std::greater<>{}); +cppsort::std_sort(collection, std::greater<>{}); ``` It is worth noting that every *comparison sorter* provided by the library transforms the comparison parameter with [`utility::as_function`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#as_function) before actually using it. It allows to use pointers to member functions of the `lhs.compare_to(rhs)` kind out-of-the-box. @@ -207,7 +207,7 @@ Note that most of the algorithms (actually, every *projection sorter* provided b ```cpp struct wrapper { int value; } std::vector vec = { {5}, {9}, {6}, {1}, {2}, {8}, {3}, {0}, {7}, {4} }; -cppsort::sort(vec, selection_sorter{}, &wrapper::value); +cppsort::selection_sort(vec, &wrapper::value); ``` Thanks to that small trick, the `selection_sorter` will sort `vec`, using the member data `wrapper::value` instead of a full `wrapper` instance (which cannot be compared) to perform the comparisons on. diff --git a/include/cpp-sort/adapters/container_aware_adapter.h b/include/cpp-sort/adapters/container_aware_adapter.h index 1412cb58..4d9adcc4 100644 --- a/include/cpp-sort/adapters/container_aware_adapter.h +++ b/include/cpp-sort/adapters/container_aware_adapter.h @@ -21,7 +21,7 @@ namespace cppsort { namespace detail { - // Hide the generic cppsort::sort + // Hide potential out-of-scope sort() struct nope_type {}; template auto sort(Args&&...) diff --git a/testsuite/utility/as_projection.cpp b/testsuite/utility/as_projection.cpp index 0eaa9667..21127d7f 100644 --- a/testsuite/utility/as_projection.cpp +++ b/testsuite/utility/as_projection.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2018 Morwenn + * Copyright (c) 2016-2020 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -10,9 +10,9 @@ #include #include #include -#include +#include +#include #include -#include #include namespace @@ -46,15 +46,17 @@ TEST_CASE( "try mixed comparison/projection function object", std::shuffle(std::begin(collection), std::end(collection), engine); tricky_function func; + cppsort::default_sorter sorter; + cppsort::stable_adapter stable_sorter; SECTION( "without an untransformed function" ) { auto vec = collection; - cppsort::sort(vec, func); + sorter(vec, func); CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); vec = collection; - cppsort::sort(std::begin(vec), std::end(vec), func); + sorter(std::begin(vec), std::end(vec), func); CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); vec = collection; @@ -62,97 +64,97 @@ TEST_CASE( "try mixed comparison/projection function object", CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); vec = collection; - cppsort::sort(cppsort::pdq_sort, vec, func); + cppsort::pdq_sort(vec, func); CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); vec = collection; - cppsort::sort(cppsort::pdq_sort, std::begin(vec), std::end(vec), func); + cppsort::pdq_sort(std::begin(vec), std::end(vec), func); CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); vec = collection; - cppsort::stable_sort(vec, func); + stable_sorter(vec, func); CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); vec = collection; - cppsort::stable_sort(std::begin(vec), std::end(vec), func); + stable_sorter(std::begin(vec), std::end(vec), func); CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); vec = collection; - cppsort::stable_sort(cppsort::pdq_sort, vec, func); + cppsort::stable_adapter{}(vec, func); CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); vec = collection; - cppsort::stable_sort(cppsort::pdq_sort, std::begin(vec), std::end(vec), func); + cppsort::stable_adapter{}(std::begin(vec), std::end(vec), func); CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); } SECTION( "with a function wrapped in as_projection" ) { auto vec = collection; - cppsort::sort(vec, cppsort::utility::as_projection(func)); + sorter(vec, cppsort::utility::as_projection(func)); CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); vec = collection; - cppsort::sort(std::begin(vec), std::end(vec), cppsort::utility::as_projection(func)); + sorter(std::begin(vec), std::end(vec), cppsort::utility::as_projection(func)); CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); vec = collection; - cppsort::sort(cppsort::pdq_sort, vec, cppsort::utility::as_projection(func)); + cppsort::pdq_sort(vec, cppsort::utility::as_projection(func)); CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); vec = collection; - cppsort::sort(cppsort::pdq_sort, std::begin(vec), std::end(vec), cppsort::utility::as_projection(func)); + cppsort::pdq_sort(std::begin(vec), std::end(vec), cppsort::utility::as_projection(func)); CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); vec = collection; - cppsort::stable_sort(vec, cppsort::utility::as_projection(func)); + stable_sorter(vec, cppsort::utility::as_projection(func)); CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); vec = collection; - cppsort::stable_sort(std::begin(vec), std::end(vec), cppsort::utility::as_projection(func)); + stable_sorter(std::begin(vec), std::end(vec), cppsort::utility::as_projection(func)); CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); vec = collection; - cppsort::stable_sort(cppsort::pdq_sort, vec, cppsort::utility::as_projection(func)); + cppsort::stable_adapter{}(vec, cppsort::utility::as_projection(func)); CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); vec = collection; - cppsort::stable_sort(cppsort::pdq_sort, std::begin(vec), std::end(vec), cppsort::utility::as_projection(func)); + cppsort::stable_adapter{}(std::begin(vec), std::end(vec), cppsort::utility::as_projection(func)); CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); } SECTION( "with a function wrapped in as_comparison" ) { auto vec = collection; - cppsort::sort(vec, cppsort::utility::as_comparison(func)); + sorter(vec, cppsort::utility::as_comparison(func)); CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); vec = collection; - cppsort::sort(std::begin(vec), std::end(vec), cppsort::utility::as_comparison(func)); + sorter(std::begin(vec), std::end(vec), cppsort::utility::as_comparison(func)); CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); vec = collection; - cppsort::sort(cppsort::pdq_sort, vec, cppsort::utility::as_comparison(func)); + cppsort::pdq_sort(vec, cppsort::utility::as_comparison(func)); CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); vec = collection; - cppsort::sort(cppsort::pdq_sort, std::begin(vec), std::end(vec), cppsort::utility::as_comparison(func)); + cppsort::pdq_sort(std::begin(vec), std::end(vec), cppsort::utility::as_comparison(func)); CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); vec = collection; - cppsort::stable_sort(vec, cppsort::utility::as_comparison(func)); + stable_sorter(vec, cppsort::utility::as_comparison(func)); CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); vec = collection; - cppsort::stable_sort(std::begin(vec), std::end(vec), cppsort::utility::as_comparison(func)); + stable_sorter(std::begin(vec), std::end(vec), cppsort::utility::as_comparison(func)); CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); vec = collection; - cppsort::stable_sort(cppsort::pdq_sort, vec, cppsort::utility::as_comparison(func)); + cppsort::stable_adapter{}(vec, cppsort::utility::as_comparison(func)); CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); vec = collection; - cppsort::stable_sort(cppsort::pdq_sort, std::begin(vec), std::end(vec), cppsort::utility::as_comparison(func)); + cppsort::stable_adapter{}(std::begin(vec), std::end(vec), cppsort::utility::as_comparison(func)); CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); } } diff --git a/testsuite/utility/as_projection_iterable.cpp b/testsuite/utility/as_projection_iterable.cpp index 562ecba7..e4e7b38e 100644 --- a/testsuite/utility/as_projection_iterable.cpp +++ b/testsuite/utility/as_projection_iterable.cpp @@ -11,9 +11,9 @@ #include #include #include -#include #include #include +#include #include namespace @@ -55,7 +55,7 @@ namespace auto operator()(Iterator first, Iterator last, Compare compare={}) const -> call { - cppsort::sort(first, last, compare); + cppsort::selection_sort(first, last, compare); return call::iterator; } @@ -69,7 +69,7 @@ namespace auto operator()(Iterable& iterable, Compare compare={}) const -> call { - cppsort::sort(iterable, compare); + cppsort::selection_sort(iterable, compare); return call::iterable; } }; @@ -87,7 +87,7 @@ namespace -> call { // Use as_projection to make an actual projection-only sorter - cppsort::sort(first, last, cppsort::utility::as_projection(projection)); + cppsort::selection_sort(first, last, cppsort::utility::as_projection(projection)); return call::iterator; } @@ -102,7 +102,7 @@ namespace -> call { // Use as_projection to make an actual projection-only sorter - cppsort::sort(iterable, cppsort::utility::as_projection(projection)); + cppsort::selection_sort(iterable, cppsort::utility::as_projection(projection)); return call::iterable; } }; @@ -136,82 +136,78 @@ TEST_CASE( "sorter_facade with sorters overloaded for iterables and mixed compar SECTION( "comparison_sorter" ) { - auto res1 = cppsort::sort(comp_sort, vec, func); + auto res1 = comp_sort(vec, func); CHECK( res1 == call::iterable ); CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); vec = collection; - auto res2 = cppsort::sort(comp_sort, std::begin(vec), std::end(vec), func); + auto res2 = comp_sort(std::begin(vec), std::end(vec), func); CHECK( res2 == call::iterator ); CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); vec = collection; - auto res3 = cppsort::sort(comp_sort, vec, cppsort::utility::as_comparison(func)); + auto res3 = comp_sort(vec, cppsort::utility::as_comparison(func)); CHECK( res3 == call::iterable ); CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); vec = collection; - auto res4 = cppsort::sort(comp_sort, std::begin(vec), std::end(vec), - cppsort::utility::as_comparison(func)); + auto res4 = comp_sort(std::begin(vec), std::end(vec), cppsort::utility::as_comparison(func)); CHECK( res4 == call::iterator ); CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); vec = collection; - auto res5 = cppsort::sort(comp_sort, vec, cppsort::utility::as_projection(func)); + auto res5 = comp_sort(vec, cppsort::utility::as_projection(func)); CHECK( res5 == call::iterable ); CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); vec = collection; - auto res6 = cppsort::sort(comp_sort, std::begin(vec), std::end(vec), - cppsort::utility::as_projection(func)); + auto res6 = comp_sort(std::begin(vec), std::end(vec), cppsort::utility::as_projection(func)); CHECK( res6 == call::iterator ); CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); vec = collection; - auto res7 = cppsort::sort(comp_sort, vec, func, - cppsort::utility::as_projection(func)); + auto res7 = comp_sort(vec, func, cppsort::utility::as_projection(func)); CHECK( res7 == call::iterable ); CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); vec = collection; - auto res8 = cppsort::sort(comp_sort, std::begin(vec), std::end(vec), func, - cppsort::utility::as_projection(func)); + auto res8 = comp_sort(std::begin(vec), std::end(vec), func, + cppsort::utility::as_projection(func)); CHECK( res8 == call::iterator ); CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); vec = collection; - auto res9 = cppsort::sort(comp_sort, vec, cppsort::utility::as_comparison(func), - cppsort::utility::as_projection(func)); + auto res9 = comp_sort(vec, cppsort::utility::as_comparison(func), + cppsort::utility::as_projection(func)); CHECK( res9 == call::iterable ); CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); vec = collection; - auto res10 = cppsort::sort(comp_sort, std::begin(vec), std::end(vec), - cppsort::utility::as_comparison(func), - cppsort::utility::as_projection(func)); + auto res10 = comp_sort(std::begin(vec), std::end(vec), + cppsort::utility::as_comparison(func), + cppsort::utility::as_projection(func)); CHECK( res10 == call::iterator ); CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); } SECTION( "projection_sorter" ) { - auto res1 = cppsort::sort(proj_sort, vec, cppsort::utility::as_projection(func)); + auto res1 = proj_sort(vec, cppsort::utility::as_projection(func)); CHECK( res1 == call::iterable ); CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); vec = collection; - auto res2 = cppsort::sort(proj_sort, std::begin(vec), std::end(vec), - cppsort::utility::as_projection(func)); + auto res2 = proj_sort(std::begin(vec), std::end(vec), cppsort::utility::as_projection(func)); CHECK( res2 == call::iterator ); CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); vec = collection; - auto res3 = cppsort::sort(proj_sort, vec, func); + auto res3 = proj_sort(vec, func); CHECK( res3 == call::iterable ); CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); vec = collection; - auto res4 = cppsort::sort(proj_sort, std::begin(vec), std::end(vec), func); + auto res4 = proj_sort(std::begin(vec), std::end(vec), func); CHECK( res4 == call::iterator ); CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); } From 6b00048da21a6701c475698a33661511055cf3ef Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 7 Nov 2020 16:06:25 +0100 Subject: [PATCH 04/90] std::identity support when available (#130) Wherever the library implements special handling of utility::identity, provide the same level of support for std::identity when it is available. --- docs/Miscellaneous-utilities.md | 6 ++- docs/Sorter-facade.md | 4 ++ include/cpp-sort/detail/config.h | 23 ++++++++++ include/cpp-sort/detail/swap_if.h | 33 ++++++++++++++- include/cpp-sort/sorter_facade.h | 70 +++++++++++++++++++++++++++++++ 5 files changed, 134 insertions(+), 2 deletions(-) diff --git a/docs/Miscellaneous-utilities.md b/docs/Miscellaneous-utilities.md index f8c46e11..6637db6b 100644 --- a/docs/Miscellaneous-utilities.md +++ b/docs/Miscellaneous-utilities.md @@ -125,6 +125,8 @@ This buffer provider allocates on the heap a number of elements depending on a g #include ``` +***WARNING:** `utility::identity` is removed in version 2.0.0, use `std::identity` instead.* + This header provides the class `projection_base` and the mechanism used to compose projections with `operator|`. See [[Chainable projections]] for more information. Also available in this header, the struct `identity` is a function object that can type any value of any movable type and return it as is. It is used as a default for every projection parameter in the library so that sorters view the values as they are by default, without a modification. @@ -141,7 +143,7 @@ struct identity: }; ``` -It is equivalent to the proposed `std::identity` from the [Ranges TS](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4560.pdf) and will probably be replaced by the standard function object the day it makes its way into the standard. +It is equivalent to the C++20 [`std::identity`](https://en.cppreference.com/w/cpp/utility/functional/identity). Wherever the documentation mentions special handling of `utility::identity`, the same support is provided for `std::identity` when it is available. This header also provides additional function objects implementing basic unary operations. These functions objects are designed to be used as *size policies* with `dynamic_buffer` and similar classes. The following function objects are available: * `half`: returns the passed value divided by 2. @@ -175,6 +177,8 @@ This utility is modeled after [`std::integral_constant`](http://en.cppreference. *New in version 1.7.0:* `projection_base` and chainable projections. +*Changed in version 1.9.0:* `std::identity` is now also supported wherever the library has special behavior for `utility::identity`. + ### `iter_move` and `iter_swap` ```cpp diff --git a/docs/Sorter-facade.md b/docs/Sorter-facade.md index c2614aeb..c7c6044b 100644 --- a/docs/Sorter-facade.md +++ b/docs/Sorter-facade.md @@ -169,4 +169,8 @@ auto operator()(Iterator first, Iterator last, -> /* implementation-defined */; ``` +When [`std::identity`](https://en.cppreference.com/w/cpp/utility/functional/identity) is available, special overloads are provided with the same behaviour as the `utility::identity` ones. + While it does not appear in this documentation, `sorter_facade` actually relies on an extensive amount of SFINAE tricks to ensure that only the `operator()` overloads that are needed and viable are generated. For example, the magic `std::less<>` overloads won't be generated if the wrapped *sorter implementation* already accepts a comparison function. + +*Changed in version 1.9.0:* when `std::identity` is available, special overloads are provided. diff --git a/include/cpp-sort/detail/config.h b/include/cpp-sort/detail/config.h index ba49a5e1..414de887 100644 --- a/include/cpp-sort/detail/config.h +++ b/include/cpp-sort/detail/config.h @@ -29,6 +29,29 @@ # define CPPSORT_CONSTEXPR_AFTER_CXX14 #endif +//////////////////////////////////////////////////////////// +// Check for C++20 features + +// There is no feature-test macro for std::identity that can +// be used reliably, so we have to fall back to checking +// compiler and standard versions + +#if defined(__GNUC__) +# if __GNUC__ > 3 && __cplusplus > 201703L +# define CPPSORT_STD_IDENTITY_AVAILABLE 1 +# else +# define CPPSORT_STD_IDENTITY_AVAILABLE 0 +# endif +#elif defined(__clang__) +# define CPPSORT_STD_IDENTITY_AVAILABLE 0 +#else +# if defined(__cpp_lib_ranges) +# CPPSORT_STD_IDENTITY_AVAILABLE 1 +# else +# CPPSORT_STD_IDENTITY_AVAILABLE 0 +# endif +#endif + //////////////////////////////////////////////////////////// // CPPSORT_ASSUME diff --git a/include/cpp-sort/detail/swap_if.h b/include/cpp-sort/detail/swap_if.h index b4636c13..af803198 100644 --- a/include/cpp-sort/detail/swap_if.h +++ b/include/cpp-sort/detail/swap_if.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2018 Morwenn + * Copyright (c) 2015-2020 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_SWAP_IF_H_ @@ -15,6 +15,7 @@ #include #include #include +#include "config.h" #include "type_traits.h" namespace cppsort @@ -81,6 +82,36 @@ namespace detail y = std::min(dx, y); } +#if CPPSORT_STD_IDENTITY_AVAILABLE + template + auto swap_if(Integer& x, Integer& y, std::less<> comp, std::identity) noexcept + -> std::enable_if_t::value> + { + return swap_if(x, y, comp, utility::identity{}); + } + + template + auto swap_if(Float& x, Float& y, std::less<> comp, std::identity) noexcept + -> std::enable_if_t::value> + { + return swap_if(x, y, comp, utility::identity{}); + } + + template + auto swap_if(Integer& x, Integer& y, std::greater<> comp, std::identity) noexcept + -> std::enable_if_t::value> + { + return swap_if(x, y, comp, utility::identity{}); + } + + template + auto swap_if(Float& x, Float& y, std::greater<> comp, std::identity) noexcept + -> std::enable_if_t::value> + { + return swap_if(x, y, comp, utility::identity{}); + } +#endif + //////////////////////////////////////////////////////////// // iter_swap_if diff --git a/include/cpp-sort/sorter_facade.h b/include/cpp-sort/sorter_facade.h index bf71505b..d11f56a3 100644 --- a/include/cpp-sort/sorter_facade.h +++ b/include/cpp-sort/sorter_facade.h @@ -499,6 +499,44 @@ namespace cppsort return operator()(std::forward(iterable)); } +#if CPPSORT_STD_IDENTITY_AVAILABLE + template + auto operator()(Iterator first, Iterator last, std::identity) const + -> std::enable_if_t< + not detail::has_projection_sort_iterator::value && + not detail::has_comparison_projection_sort_iterator< + Sorter, + Iterator, + std::less<>, + std::identity + >::value, + decltype(Sorter::operator()(std::move(first), std::move(last))) + > + { + return Sorter::operator()(std::move(first), std::move(last)); + } + + template + auto operator()(Iterable&& iterable, std::identity) const + -> std::enable_if_t< + not detail::has_projection_sort_iterator< + Sorter, + decltype(std::begin(iterable)), + std::identity + >::value && + not detail::has_comparison_projection_sort_iterator< + Sorter, + decltype(std::begin(iterable)), + std::less<>, + std::identity + >::value, + decltype(operator()(std::forward(iterable))) + > + { + return operator()(std::forward(iterable)); + } +#endif + //////////////////////////////////////////////////////////// // Fused comparison-projection overloads @@ -599,6 +637,38 @@ namespace cppsort return operator()(std::forward(iterable)); } +#if CPPSORT_STD_IDENTITY_AVAILABLE + template + auto operator()(Iterator first, Iterator last, std::less<>, std::identity) const + -> std::enable_if_t< + not detail::has_comparison_projection_sort_iterator< + Sorter, + Iterator, + std::less<>, + std::identity + >::value, + decltype(Sorter::operator()(std::move(first), std::move(last))) + > + { + return Sorter::operator()(std::move(first), std::move(last)); + } + + template + auto operator()(Iterable&& iterable, std::less<>, std::identity) const + -> std::enable_if_t< + not detail::has_comparison_projection_sort_iterator< + Sorter, + decltype(std::begin(iterable)), + std::less<>, + std::identity + >::value, + decltype(operator()(std::forward(iterable))) + > + { + return operator()(std::forward(iterable)); + } +#endif + template auto operator()(Iterator first, Iterator last, std::less<>, Projection projection) const -> std::enable_if_t< From 3428fb4a5367cc8849267d1baad7ce2fc95c9d85 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 9 Nov 2020 01:06:42 +0100 Subject: [PATCH 05/90] Add branchless trait specialization for std::identity (issue #130) --- include/cpp-sort/utility/branchless_traits.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/cpp-sort/utility/branchless_traits.h b/include/cpp-sort/utility/branchless_traits.h index 397f6f28..9691d839 100644 --- a/include/cpp-sort/utility/branchless_traits.h +++ b/include/cpp-sort/utility/branchless_traits.h @@ -10,6 +10,7 @@ //////////////////////////////////////////////////////////// #include #include +#include "../detail/config.h" #include "../detail/type_traits.h" namespace cppsort @@ -83,6 +84,13 @@ namespace utility struct is_probably_branchless_projection_impl: std::is_member_object_pointer {}; + +#if CPPSORT_STD_IDENTITY_AVAILABLE + template + struct is_probably_branchless_projection_impl: + std::true_type + {}; +#endif } // Strip types from cv and reference qualifications if needed From 25c361a2b2ea56c99e5c26caa2676fe723fee898 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 9 Nov 2020 12:25:30 +0100 Subject: [PATCH 06/90] Conditional support for std::ranges::less and std::ranges::greater (issue #130) --- docs/Miscellaneous-utilities.md | 9 +- docs/Sorter-facade.md | 4 + docs/Sorters.md | 8 +- include/cpp-sort/detail/swap_if.h | 58 +++++ include/cpp-sort/detail/three_way_compare.h | 61 ++++- include/cpp-sort/sorter_facade.h | 218 +++++++++++++++++- include/cpp-sort/sorters/counting_sorter.h | 21 +- .../spread_sorter/string_spread_sorter.h | 55 ++++- include/cpp-sort/utility/branchless_traits.h | 14 ++ 9 files changed, 431 insertions(+), 17 deletions(-) diff --git a/docs/Miscellaneous-utilities.md b/docs/Miscellaneous-utilities.md index 6637db6b..ec6658ca 100644 --- a/docs/Miscellaneous-utilities.md +++ b/docs/Miscellaneous-utilities.md @@ -79,8 +79,8 @@ constexpr bool is_probably_branchless_comparison_v ``` This trait tells whether the comparison function `Compare` is likely to generate branchless code when comparing two instances of `T`. By default it considers that the following comparison functions are likely to be branchless: -* `std::less<>` and `std::less` for any `T` which satisfies [`std::is_arithmetic`](http://en.cppreference.com/w/cpp/types/is_arithmetic) -* `std::greater<>` and `std::greater` for any `T` which satisfies [`std::is_arithmetic`](http://en.cppreference.com/w/cpp/types/is_arithmetic) +* `std::less<>`, `std::ranges::less` and `std::less` for any `T` which satisfies [`std::is_arithmetic`](http://en.cppreference.com/w/cpp/types/is_arithmetic) +* `std::greater<>`, `std::ranges::greater` and `std::greater` for any `T` which satisfies [`std::is_arithmetic`](http://en.cppreference.com/w/cpp/types/is_arithmetic) ```cpp template @@ -93,10 +93,15 @@ constexpr bool is_probably_branchless_projection_v This trait tells whether the projection function `Projection` is likely to generate branchless code when called with an instance of `T`. By default it considers that the following projection functions are likely to be branchless: * `cppsort::utility::identity` for any type +* `std::identity` for any type (when available) * Any type that satisfies [`std::is_member_function_pointer`](http://en.cppreference.com/w/cpp/types/is_member_function_pointer) provided it is called with an instance of the appropriate class These traits can be specialized for user-defined types. If one of the traits is specialized to consider that a user-defined type is likely to be branchless with a comparison/projection function, cv-qualified and reference-qualified versions of the same user-defined type will also be considered to produce branchless code when compared/projected with the same function. +*Changed in version 1.9.0:* conditional support for [`std::ranges::less`](https://en.cppreference.com/w/cpp/utility/functional/ranges/less) and [`std::ranges::greater`](https://en.cppreference.com/w/cpp/utility/functional/ranges/greater). + +*Changed in version 1.9.0:* conditional support for [`std::identity`](https://en.cppreference.com/w/cpp/utility/functional/identity). + ### Buffer providers ```cpp diff --git a/docs/Sorter-facade.md b/docs/Sorter-facade.md index c7c6044b..06368774 100644 --- a/docs/Sorter-facade.md +++ b/docs/Sorter-facade.md @@ -171,6 +171,10 @@ auto operator()(Iterator first, Iterator last, When [`std::identity`](https://en.cppreference.com/w/cpp/utility/functional/identity) is available, special overloads are provided with the same behaviour as the `utility::identity` ones. +When [`std::ranges::less`](https://en.cppreference.com/w/cpp/utility/functional/ranges/less) is available, special overloads are provided with a behaviour similar to that of the `std::less<>` ones. + While it does not appear in this documentation, `sorter_facade` actually relies on an extensive amount of SFINAE tricks to ensure that only the `operator()` overloads that are needed and viable are generated. For example, the magic `std::less<>` overloads won't be generated if the wrapped *sorter implementation* already accepts a comparison function. *Changed in version 1.9.0:* when `std::identity` is available, special overloads are provided. + +*Changed in version 1.9.0:* when `std::ranges::less` is available, special overloads are provided. diff --git a/docs/Sorters.md b/docs/Sorters.md index 8b4302b9..dd0a9234 100644 --- a/docs/Sorters.md +++ b/docs/Sorters.md @@ -400,7 +400,7 @@ The following sorters are available but will only work for some specific types i #include ``` -`counting_sorter` implements a simple [counting sort](https://en.wikipedia.org/wiki/Counting_sort). This sorter also supports reverse sorting with `std::greater<>`. +`counting_sorter` implements a simple [counting sort](https://en.wikipedia.org/wiki/Counting_sort). This sorter also supports reverse sorting with `std::greater<>` or `std::ranges::greater`. | Best | Average | Worst | Memory | Stable | Iterators | | ----------- | ----------- | ----------- | ----------- | ----------- | ------------- | @@ -412,6 +412,8 @@ This sorter works with any type satisfying the trait `std::is_integral` (as well *Changed in version 1.6.0:* support for `[un]signed __int128`. +*Changed in version 1.9.0:* conditional support for [`std::ranges::greater`](https://en.cppreference.com/w/cpp/utility/functional/ranges/greater). + ### `ska_sorter` ```cpp @@ -451,7 +453,7 @@ It comes into three main flavours (available individually if needed): * `integer_spread_sorter` works with any type satisfying the trait `std::is_integral`. * `float_spread_sorter` works with any type satisfying the trait `std::numeric_limits::is_iec559` whose size is the same as `std::uint32_t` or `std::uin64_t`. -* `string_spread_sorter` works with `std::string` and `std::wstring` (if `wchar_t` is 2 bytes). This sorter also supports reverse sorting with `std::greater<>`. In C++17 it also works with `std::string_view` and `std::wstring_view` (if `wchar_t` is 2 bytes). +* `string_spread_sorter` works with `std::string` and `std::wstring` (if `wchar_t` is 2 bytes). This sorter also supports reverse sorting with `std::greater<>` and `std::ranges::greater`. In C++17 it also works with `std::string_view` and `std::wstring_view` (if `wchar_t` is 2 bytes). These sorters accept projections as long as their simplest form can handle the result of the projection. The three of them are aggregated into one main sorter the following way: @@ -464,3 +466,5 @@ struct spread_sorter: > {}; ``` + +*Changed in version 1.9.0:* conditional support for [`std::ranges::greater`](https://en.cppreference.com/w/cpp/utility/functional/ranges/greater). diff --git a/include/cpp-sort/detail/swap_if.h b/include/cpp-sort/detail/swap_if.h index af803198..ecb47b87 100644 --- a/include/cpp-sort/detail/swap_if.h +++ b/include/cpp-sort/detail/swap_if.h @@ -112,6 +112,64 @@ namespace detail } #endif +#ifdef __cpp_lib_ranges + template + auto swap_if(Integer& x, Integer& y, std::ranges::less comp, utility::identity) noexcept + -> std::enable_if_t::value> + { + return swap_if(x, y, comp, utility::identity{}); + } + + template + auto swap_if(Float& x, Float& y, std::ranges::less comp, utility::identity) noexcept + -> std::enable_if_t::value> + { + return swap_if(x, y, comp, utility::identity{}); + } + + template + auto swap_if(Integer& x, Integer& y, std::ranges::greater comp, utility::identity) noexcept + -> std::enable_if_t::value> + { + return swap_if(x, y, comp, utility::identity{}); + } + + template + auto swap_if(Float& x, Float& y, std::ranges::greater comp, utility::identity) noexcept + -> std::enable_if_t::value> + { + return swap_if(x, y, comp, utility::identity{}); + } + + template + auto swap_if(Integer& x, Integer& y, std::ranges::less comp, std::identity) noexcept + -> std::enable_if_t::value> + { + return swap_if(x, y, comp, utility::identity{}); + } + + template + auto swap_if(Float& x, Float& y, std::ranges::less comp, std::identity) noexcept + -> std::enable_if_t::value> + { + return swap_if(x, y, comp, utility::identity{}); + } + + template + auto swap_if(Integer& x, Integer& y, std::ranges::greater comp, std::identity) noexcept + -> std::enable_if_t::value> + { + return swap_if(x, y, comp, utility::identity{}); + } + + template + auto swap_if(Float& x, Float& y, std::ranges::greater comp, std::identity) noexcept + -> std::enable_if_t::value> + { + return swap_if(x, y, comp, utility::identity{}); + } +#endif + //////////////////////////////////////////////////////////// // iter_swap_if diff --git a/include/cpp-sort/detail/three_way_compare.h b/include/cpp-sort/detail/three_way_compare.h index 6a77a631..86910fbc 100644 --- a/include/cpp-sort/detail/three_way_compare.h +++ b/include/cpp-sort/detail/three_way_compare.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2019 Morwenn + * Copyright (c) 2016-2020 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_THREE_WAY_COMPARE_H_ @@ -141,6 +141,35 @@ namespace detail } }; +#ifdef __cpp_lib_ranges + template<> + struct three_way_compare: + three_way_compare_base> + { + constexpr three_way_compare(std::ranges::less) {} + + using three_way_compare_base>::operator(); + + template< + typename CharT, + typename Traits1, typename Alloc1, + typename Traits2, typename Alloc2 + > + auto operator()(const std::basic_string& lhs, + const std::basic_string& rhs) const + -> int + { + return lhs.compare(0, lhs.size(), rhs.data(), rhs.size()); + } + + constexpr auto base() const noexcept + -> std::ranges::less + { + return {}; + } + }; +#endif + template<> struct three_way_compare, true>: three_way_compare_base>> @@ -168,6 +197,36 @@ namespace detail return {}; } }; + +#ifdef __cpp_lib_ranges + template<> + struct three_way_compare: + three_way_compare_base> + { + constexpr three_way_compare(std::ranges::greater) {} + + using three_way_compare_base>::operator(); + + template< + typename CharT, + typename Traits1, typename Alloc1, + typename Traits2, typename Alloc2 + > + auto operator()(const std::basic_string& lhs, + const std::basic_string& rhs) const + -> int + { + int res = lhs.compare(0, lhs.size(), rhs.data(), rhs.size()); + return (res < 0) ? 1 : -res; + } + + constexpr auto base() const noexcept + -> std::ranges::greater + { + return {}; + } + }; +#endif }} #endif // CPPSORT_DETAIL_THREE_WAY_COMPARE_H_ diff --git a/include/cpp-sort/sorter_facade.h b/include/cpp-sort/sorter_facade.h index d11f56a3..36e6d1bc 100644 --- a/include/cpp-sort/sorter_facade.h +++ b/include/cpp-sort/sorter_facade.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2019 Morwenn + * Copyright (c) 2015-2020 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORTER_FACADE_H_ @@ -460,6 +460,32 @@ namespace cppsort return operator()(std::forward(iterable)); } +#ifdef __cpp_lib_ranges + template + auto operator()(Iterator first, Iterator last, std::ranges::less) const + -> std::enable_if_t< + not detail::has_comparison_sort_iterator::value, + decltype(Sorter::operator()(std::move(first), std::move(last))) + > + { + return Sorter::operator()(std::move(first), std::move(last)); + } + + template + auto operator()(Iterable&& iterable, std::ranges::less) const + -> std::enable_if_t< + not detail::has_comparison_sort_iterator< + Sorter, + decltype(std::begin(iterable)), + std::ranges::less + >::value, + decltype(operator()(std::forward(iterable))) + > + { + return operator()(std::forward(iterable)); + } +#endif + //////////////////////////////////////////////////////////// // utility::identity overloads @@ -670,7 +696,7 @@ namespace cppsort #endif template - auto operator()(Iterator first, Iterator last, std::less<>, Projection projection) const + auto operator()(Iterator first, Iterator last, std::less<> compare, Projection projection) const -> std::enable_if_t< detail::has_comparison_projection_sort_iterator< Sorter, @@ -678,11 +704,11 @@ namespace cppsort std::less<>, refined_t >::value, - decltype(Sorter::operator()(std::move(first), std::move(last), std::less<>{}, + decltype(Sorter::operator()(std::move(first), std::move(last), compare, refined(std::move(projection)))) > { - return Sorter::operator()(std::move(first), std::move(last), std::less<>{}, + return Sorter::operator()(std::move(first), std::move(last), compare, refined(std::move(projection))); } @@ -709,7 +735,7 @@ namespace cppsort } template - auto operator()(Iterable&& iterable, std::less<>, Projection projection) const + auto operator()(Iterable&& iterable, std::less<> compare, Projection projection) const -> std::enable_if_t< detail::has_comparison_projection_sort< Sorter, @@ -717,16 +743,16 @@ namespace cppsort std::less<>, refined_t >::value, - decltype(Sorter::operator()(std::forward(iterable), std::less<>{}, + decltype(Sorter::operator()(std::forward(iterable), compare, refined(std::move(projection)))) > { - return Sorter::operator()(std::forward(iterable), std::less<>{}, + return Sorter::operator()(std::forward(iterable), compare, refined(std::move(projection))); } template - auto operator()(Iterable&& iterable, std::less<>, Projection projection) const + auto operator()(Iterable&& iterable, std::less<> compare, Projection projection) const -> std::enable_if_t< not detail::has_comparison_projection_sort< Sorter, @@ -740,11 +766,11 @@ namespace cppsort std::less<>, refined_t >::value, - decltype(Sorter::operator()(std::begin(iterable), std::end(iterable), std::less<>{}, + decltype(Sorter::operator()(std::begin(iterable), std::end(iterable), compare, refined(std::move(projection)))) > { - return Sorter::operator()(std::begin(iterable), std::end(iterable), std::less<>{}, + return Sorter::operator()(std::begin(iterable), std::end(iterable), compare, refined(std::move(projection))); } @@ -809,6 +835,178 @@ namespace cppsort refined(std::move(projection))); } +#ifdef __cpp_lib_ranges + template + auto operator()(Iterator first, Iterator last, std::ranges::less, std::identity) const + -> std::enable_if_t< + not detail::has_comparison_projection_sort_iterator< + Sorter, + Iterator, + std::ranges::less, + std::identity + >::value, + decltype(Sorter::operator()(std::move(first), std::move(last))) + > + { + return Sorter::operator()(std::move(first), std::move(last)); + } + + template + auto operator()(Iterable&& iterable, std::ranges::less, std::identity) const + -> std::enable_if_t< + not detail::has_comparison_projection_sort_iterator< + Sorter, + decltype(std::begin(iterable)), + std::ranges::less, + std::identity + >::value, + decltype(operator()(std::forward(iterable))) + > + { + return operator()(std::forward(iterable)); + } + + template + auto operator()(Iterator first, Iterator last, std::ranges::less compare, Projection projection) const + -> std::enable_if_t< + detail::has_comparison_projection_sort_iterator< + Sorter, + Iterator, + std::ranges::less, + refined_t + >::value, + decltype(Sorter::operator()(std::move(first), std::move(last), compare, + refined(std::move(projection)))) + > + { + return Sorter::operator()(std::move(first), std::move(last), compare, + refined(std::move(projection))); + } + + template + auto operator()(Iterator first, Iterator last, std::ranges::less, Projection projection) const + -> std::enable_if_t< + not detail::has_comparison_projection_sort_iterator< + Sorter, + Iterator, + std::ranges::less, + refined_t + >::value && + detail::has_projection_sort_iterator< + Sorter, + Iterator, + refined_t + >::value, + decltype(Sorter::operator()(std::move(first), std::move(last), + refined(std::move(projection)))) + > + { + return Sorter::operator()(std::move(first), std::move(last), + refined(std::move(projection))); + } + + template + auto operator()(Iterable&& iterable, std::ranges::less compare, Projection projection) const + -> std::enable_if_t< + detail::has_comparison_projection_sort< + Sorter, + Iterable, + std::ranges::less, + refined_t + >::value, + decltype(Sorter::operator()(std::forward(iterable), compare, + refined(std::move(projection)))) + > + { + return Sorter::operator()(std::forward(iterable), compare, + refined(std::move(projection))); + } + + template + auto operator()(Iterable&& iterable, std::ranges::less compare, Projection projection) const + -> std::enable_if_t< + not detail::has_comparison_projection_sort< + Sorter, + Iterable, + std::ranges::less, + refined_t + >::value && + detail::has_comparison_projection_sort_iterator< + Sorter, + decltype(std::begin(iterable)), + std::ranges::less, + refined_t + >::value, + decltype(Sorter::operator()(std::begin(iterable), std::end(iterable), compare, + refined(std::move(projection)))) + > + { + return Sorter::operator()(std::begin(iterable), std::end(iterable), compare, + refined(std::move(projection))); + } + + template + auto operator()(Iterable&& iterable, std::ranges::less, Projection projection) const + -> std::enable_if_t< + not detail::has_comparison_projection_sort< + Sorter, + Iterable, + std::ranges::less, + refined_t + >::value && + not detail::has_comparison_projection_sort_iterator< + Sorter, + decltype(std::begin(iterable)), + std::ranges::less, + refined_t + >::value && + detail::has_projection_sort< + Sorter, + Iterable, + refined_t + >::value, + decltype(Sorter::operator()(std::forward(iterable), + refined(std::move(projection)))) + > + { + return Sorter::operator()(std::forward(iterable), + refined(std::move(projection))); + } + + template + auto operator()(Iterable&& iterable, std::ranges::less, Projection projection) const + -> std::enable_if_t< + not detail::has_comparison_projection_sort< + Sorter, + Iterable, + std::ranges::less, + refined_t + >::value && + not detail::has_comparison_projection_sort_iterator< + Sorter, + decltype(std::begin(iterable)), + std::ranges::less, + refined_t + >::value && + not detail::has_projection_sort< + Sorter, + Iterable, + refined_t + >::value && + detail::has_projection_sort_iterator< + Sorter, + decltype(std::begin(iterable)), + refined_t + >::value, + decltype(Sorter::operator()(std::begin(iterable), std::end(iterable), + refined(std::move(projection)))) + > + { + return Sorter::operator()(std::begin(iterable), std::end(iterable), + refined(std::move(projection))); + } +#endif + //////////////////////////////////////////////////////////// // Embed projection in comparison diff --git a/include/cpp-sort/sorters/counting_sorter.h b/include/cpp-sort/sorters/counting_sorter.h index 693ff7ea..ec00aa09 100644 --- a/include/cpp-sort/sorters/counting_sorter.h +++ b/include/cpp-sort/sorters/counting_sorter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2019 Morwenn + * Copyright (c) 2016-2020 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORTERS_COUNTING_SORTER_H_ @@ -61,11 +61,30 @@ namespace cppsort reverse_counting_sort(std::move(first), std::move(last)); } +#ifdef __cpp_lib_ranges + template + auto operator()(ForwardIterator first, ForwardIterator last, std::ranges::greater) const + -> std::enable_if_t< + detail::is_integral>::value + > + { + static_assert( + std::is_base_of< + std::forward_iterator_tag, + iterator_category_t + >::value, + "counting_sorter requires at least forward iterators" + ); + + reverse_counting_sort(std::move(first), std::move(last)); + } + //////////////////////////////////////////////////////////// // Sorter traits using iterator_category = std::forward_iterator_tag; using is_always_stable = std::false_type; +#endif }; } 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 680237d0..aac66f71 100644 --- a/include/cpp-sort/sorters/spread_sorter/string_spread_sorter.h +++ b/include/cpp-sort/sorters/spread_sorter/string_spread_sorter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2018 Morwenn + * Copyright (c) 2015-2020 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORTERS_SPREAD_SORTER_STRING_SPREAD_SORTER_H_ @@ -148,6 +148,59 @@ namespace cppsort unused); } +#ifdef __cpp_lib_ranges + template< + typename RandomAccessIterator, + typename Projection = utility::identity + > + auto operator()(RandomAccessIterator first, RandomAccessIterator last, + std::ranges::greater compare, Projection projection={}) const + -> std::enable_if_t< + std::is_same_v, std::string> + || std::is_same_v, std::string_view> + > + { + static_assert( + std::is_base_of_v< + std::random_access_iterator_tag, + iterator_category_t + >, + "string_spread_sorter requires at least random-access iterators" + ); + + unsigned char unused = '\0'; + spreadsort::reverse_string_sort(std::move(first), std::move(last), + std::move(compare), std::move(projection), + unused); + } + + template< + typename RandomAccessIterator, + typename Projection = utility::identity + > + auto operator()(RandomAccessIterator first, RandomAccessIterator last, + std::ranges::greater compare, Projection projection={}) const + -> std::enable_if_t<( + std::is_same_v, std::wstring> + || std::is_same_v, std::wstring_view> + ) && (sizeof(wchar_t) == 2) + > + { + static_assert( + std::is_base_of_v< + std::random_access_iterator_tag, + iterator_category_t + >, + "string_spread_sorter requires at least random-access iterators" + ); + + std::uint16_t unused = 0; + spreadsort::reverse_string_sort(std::move(first), std::move(last), + std::move(compare), std::move(projection), + unused); + } +#endif + //////////////////////////////////////////////////////////// // Sorter traits diff --git a/include/cpp-sort/utility/branchless_traits.h b/include/cpp-sort/utility/branchless_traits.h index 9691d839..5ec0e38e 100644 --- a/include/cpp-sort/utility/branchless_traits.h +++ b/include/cpp-sort/utility/branchless_traits.h @@ -33,6 +33,13 @@ namespace utility std::is_arithmetic {}; +#ifdef __cpp_lib_ranges + template + struct is_probably_branchless_comparison_impl: + std::is_arithmetic + {}; +#endif + template struct is_probably_branchless_comparison_impl, T>: std::is_arithmetic @@ -43,6 +50,13 @@ namespace utility std::is_arithmetic {}; +#ifdef __cpp_lib_ranges + template + struct is_probably_branchless_comparison_impl: + std::is_arithmetic + {}; +#endif + template struct is_probably_branchless_comparison_impl, T>: std::is_arithmetic From f60a70ccb851d2d921d39fd3626b0021543346bb Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 9 Nov 2020 16:05:24 +0100 Subject: [PATCH 07/90] Update Changelog.md Add new C++20 category to describe the new support for std::identity, std::ranges::less and std::ranges::greater. Remove the section about how bitmap_allocator might be used and the text about the relevance of the page across old versions. The embedding of the documentation into the main repository made that text useless (issue #157). [ci skip] --- docs/Changelog.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/Changelog.md b/docs/Changelog.md index b333faaa..1cbf9cd7 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -1,7 +1,5 @@ This page describes the features that change in **cpp-sort** depending on the C++ version with which it is compiled (C++14 or later) as well as the support for miscellaneous compiler extensions; for a full changelog between actual releases, you can check the dedicated [releases page](https://github.com/Morwenn/cpp-sort/releases). -*The notes in this page are only valid for the latest versions of the 1.x and 2.x branches. If you are using an older version of the library, some of them might not apply.* - ## C++14 features While **cpp-sort** theoretically requires a fully C++14-compliant compiler, a few standard features are either not available or deactivated in popular compilers and the library tries to take those into account if possible. @@ -11,7 +9,7 @@ While **cpp-sort** theoretically requires a fully C++14-compliant compiler, a fe ## C++17 features -When compiled with C++17, **cpp-sort** might gain a few additional features depending on the level of C++17 support provided by the compiler. The availability of most of the features depend on the presence of corresponding [feature-testing macros](https://wg21.link/SD6). The support for feature-testing macros being optional, it is possible that one of the features listed below isn't available even though the compiler is supposed to provide enough C++17 features to support it. If it is the case and it is a problem for you, don't hesitate to open an issue so that we can explicitly support the given compiler. +When compiled with C++17, **cpp-sort** might gain a few additional features depending on the level of C++17 support provided by the compiler. The availability of most of the features depends on the presence of corresponding [feature-testing macros](https://wg21.link/SD6). The support for feature-testing macros being optional in C++17, it is possible that some of the features listed below aren't available even though the compiler is implements them. If it is the case and it is a problem for you, don't hesitate to open an issue so that we can explicitly support the given compiler. **New features:** * `string_spread_sort` now accepts [`std::string_view`](https://en.cppreference.com/w/cpp/string/basic_string_view) and sometimes `std::wstring_view`. @@ -61,6 +59,15 @@ When compiled with C++17, **cpp-sort** might gain a few additional features depe The C++17 traits are used as is when the feature-test macro `__cpp_lib_is_invocable` is defined. +## C++20 features + +When compiled with C++20, **cpp-sort** might gain a few additional features depending on the level of C++20 support provided by the compiler. The availability of those features depends on the presence of corresponding [feature-testing macros](https://wg21.link/SD6) when possible, even though some checks are more granular. Don't hesitate to open an issue if your compiler and standard library supports one of those features but it doesn't seem to work in **cpp-sort**. + +**New features:** +* When available, [`std::identity`](https://en.cppreference.com/w/cpp/utility/functional/identity) benefits from dedicated support wherever [`utility::identity`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#miscellaneous-function-objects) is supported, with equivalent semantics. + +* When available, [`std::ranges::less`](https://en.cppreference.com/w/cpp/utility/functional/ranges/less) and [`std::ranges::greater`](https://en.cppreference.com/w/cpp/utility/functional/ranges/greater) benefit from dedicated support wherever [`std::less<>`](https://en.cppreference.com/w/cpp/utility/functional/less_void) and [`std::greater<>`](https://en.cppreference.com/w/cpp/utility/functional/greater_void) are supported, with equivalent semantics. + ## Other features **cpp-sort** tries to take advantage of more than just standard features when possible, and also to provide extended support for some compiler-specific extensions. Below is a list of the impact that non-standard features might have on the library: @@ -69,10 +76,4 @@ When compiled with C++17, **cpp-sort** might gain a few additional features depe * 128-bit integers support: `ska_sorter` has dedicated support for 128-bit integers (`unsigned __int128` or `__uint128_t` and its signed counterpart), no matter whether the standard library is also instrumented for those types. This support should be available as long as `__SIZEOF_INT128__` is defined by the compiler. **Performance improvements:** -* Additional allocators: `merge_insertion_sorter` can be somewhat more performant when libstdc++'s [`bitmap_allocator`](https://gcc.gnu.org/onlinedocs/libstdc++/manual/bitmap_allocator.html) is available. - - This improvement is made available through the check `__has_include()`, which means that it should be available for every compiler where `__has_include` and libstdc++ are available (old and new Clang, and more recent GCC). - - *Changed in version 1.7.0:* `merge_insertion_sorter` uses a custom list implementation which does not need to take advantage of `bitmap_allocator` anymore. - * Bit manipulation intrinsics: there are a few places where bit tricks are used to perform a few operations faster. Some of those operations are made faster with bitwise manipulation intrinsics when those are available. From 24ad4045c5ea1da3bc6b9a842999b92d731704af Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 9 Nov 2020 18:51:33 +0100 Subject: [PATCH 08/90] Remove useless default parameters from counting_adapter::operator() --- include/cpp-sort/adapters/counting_adapter.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/cpp-sort/adapters/counting_adapter.h b/include/cpp-sort/adapters/counting_adapter.h index 92c07b08..f553cea6 100644 --- a/include/cpp-sort/adapters/counting_adapter.h +++ b/include/cpp-sort/adapters/counting_adapter.h @@ -77,8 +77,7 @@ namespace cppsort is_projection_v > > - auto operator()(Iterable&& iterable, Compare compare={}, - Projection projection={}) const + auto operator()(Iterable&& iterable, Compare compare, Projection projection) const -> CountType { CountType count(0); From 79261300ef7c815340c76965213e7b1712f65b9b Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 10 Nov 2020 17:28:35 +0100 Subject: [PATCH 09/90] Small tweaks Mostly small things that I noticed one working on the 2.0.0 branch, which will undoubtly result in merge issues later, but these ought to be fixed in 1.x.y anyway. --- include/cpp-sort/adapters/schwartz_adapter.h | 1 + include/cpp-sort/detail/merge_insertion_sort.h | 1 + include/cpp-sort/detail/spreadsort/integer_sort.h | 11 +++-------- .../sorters/spread_sorter/float_spread_sorter.h | 3 ++- testsuite/adapters/mixed_adapters.cpp | 1 + testsuite/adapters/return_forwarding.cpp | 3 ++- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/include/cpp-sort/adapters/schwartz_adapter.h b/include/cpp-sort/adapters/schwartz_adapter.h index 698cc396..f609b582 100644 --- a/include/cpp-sort/adapters/schwartz_adapter.h +++ b/include/cpp-sort/adapters/schwartz_adapter.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include "../detail/associate_iterator.h" #include "../detail/checkers.h" diff --git a/include/cpp-sort/detail/merge_insertion_sort.h b/include/cpp-sort/detail/merge_insertion_sort.h index b0c85c05..58f738b8 100644 --- a/include/cpp-sort/detail/merge_insertion_sort.h +++ b/include/cpp-sort/detail/merge_insertion_sort.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include "config.h" #include "fixed_size_list.h" diff --git a/include/cpp-sort/detail/spreadsort/integer_sort.h b/include/cpp-sort/detail/spreadsort/integer_sort.h index bc61cea2..06d37ba6 100644 --- a/include/cpp-sort/detail/spreadsort/integer_sort.h +++ b/include/cpp-sort/detail/spreadsort/integer_sort.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2016 Morwenn + * Copyright (c) 2015-2020 Morwenn * SPDX-License-Identifier: MIT */ @@ -28,7 +28,6 @@ Doxygen comments by Paul A. Bristow Jan 2015 #include #include #include -#include #include "detail/constants.h" #include "detail/integer_sort.h" #include "../pdqsort.h" @@ -80,12 +79,8 @@ Some performance plots of runtime vs. n and log(range) are provided:\n \remark * S is a constant called max_splits, defaulting to 11 (except for strings where it is the log of the character size). */ - template< - typename RandomAccessIter, - typename Projection = utility::identity - > - auto integer_sort(RandomAccessIter first, RandomAccessIter last, - Projection projection={}) + template + auto integer_sort(RandomAccessIter first, RandomAccessIter last, Projection projection) -> void { auto&& proj = utility::as_function(projection); 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 ab8f2ada..8873fb74 100644 --- a/include/cpp-sort/sorters/spread_sorter/float_spread_sorter.h +++ b/include/cpp-sort/sorters/spread_sorter/float_spread_sorter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2017 Morwenn + * Copyright (c) 2015-2020 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORTERS_SPREAD_SORTER_FLOAT_SPREAD_SORTER_H_ @@ -15,6 +15,7 @@ #include #include #include +#include #include #include "../../detail/iterator_traits.h" #include "../../detail/spreadsort/float_sort.h" diff --git a/testsuite/adapters/mixed_adapters.cpp b/testsuite/adapters/mixed_adapters.cpp index 929020e4..b1003bdc 100644 --- a/testsuite/adapters/mixed_adapters.cpp +++ b/testsuite/adapters/mixed_adapters.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include diff --git a/testsuite/adapters/return_forwarding.cpp b/testsuite/adapters/return_forwarding.cpp index ccc85d79..f2064c97 100644 --- a/testsuite/adapters/return_forwarding.cpp +++ b/testsuite/adapters/return_forwarding.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Morwenn + * Copyright (c) 2018-2020 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -10,6 +10,7 @@ #include #include #include +#include namespace { From 478909f499190a676ec62659ccf6147180c593df Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 10 Nov 2020 19:49:01 +0100 Subject: [PATCH 10/90] More std::identity support for schwartz_adapter (issue #130) --- include/cpp-sort/adapters/schwartz_adapter.h | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/include/cpp-sort/adapters/schwartz_adapter.h b/include/cpp-sort/adapters/schwartz_adapter.h index f609b582..f135cda1 100644 --- a/include/cpp-sort/adapters/schwartz_adapter.h +++ b/include/cpp-sort/adapters/schwartz_adapter.h @@ -22,6 +22,7 @@ #include #include "../detail/associate_iterator.h" #include "../detail/checkers.h" +#include "../detail/config.h" #include "../detail/iterator_traits.h" #include "../detail/memory.h" #include "../detail/type_traits.h" @@ -177,6 +178,25 @@ namespace cppsort // utility::identity does nothing, bypass schartz_adapter entirely return this->get()(std::move(first), std::move(last), std::move(compare), projection); } + +#if CPPSORT_STD_IDENTITY_AVAILABLE + template + auto operator()(ForwardIterable&& iterable, Compare compare, std::identity projection) const + -> decltype(this->get()(std::forward(iterable), std::move(compare), projection)) + { + // std::identity does nothing, bypass schartz_adapter entirely + return this->get()(std::forward(iterable), std::move(compare), projection); + } + + template + auto operator()(ForwardIterator first, ForwardIterator last, + Compare compare, std::identity projection) const + -> decltype(this->get()(std::move(first), std::move(last), std::move(compare), projection)) + { + // std::identity does nothing, bypass schartz_adapter entirely + return this->get()(std::move(first), std::move(last), std::move(compare), projection); + } +#endif }; } From 8a71b8d977d985d24ad0faada917e8125858223b Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 22 Nov 2020 14:02:58 +0100 Subject: [PATCH 11/90] Revamp the bidirectional version of vergesort The bidirectional version of vergesort was modified as follows: - It now uses the same k-way merge as the random-access version. - It creates fewer partitions with one element, generally being smarter about which run an element should belong to. - It spends less time recomputing runs sizes, leading to a lower impact on the running time overall. The k-way merge algorithm is now shared between both versions and has been tweaked to handle 1-element runs better. The in-code documentation was also reworked to be more comprehensible and comprehensive. Some variable names were changed to make the logic more obvious. --- include/cpp-sort/adapters/verge_adapter.h | 6 +- include/cpp-sort/detail/vergesort.h | 432 ++++++++++++++-------- include/cpp-sort/sorters/verge_sorter.h | 10 +- 3 files changed, 288 insertions(+), 160 deletions(-) diff --git a/include/cpp-sort/adapters/verge_adapter.h b/include/cpp-sort/adapters/verge_adapter.h index 171a9c6f..3bb8ed8e 100644 --- a/include/cpp-sort/adapters/verge_adapter.h +++ b/include/cpp-sort/adapters/verge_adapter.h @@ -55,9 +55,9 @@ namespace cppsort "verge_adapter requires at least random-access iterators" ); - vergesort(std::move(first), std::move(last), last - first, - std::move(compare), std::move(projection), - this->get()); + verge::sort(std::move(first), std::move(last), last - first, + std::move(compare), std::move(projection), + this->get()); } //////////////////////////////////////////////////////////// diff --git a/include/cpp-sort/detail/vergesort.h b/include/cpp-sort/detail/vergesort.h index faaa3e0f..408a17fa 100644 --- a/include/cpp-sort/detail/vergesort.h +++ b/include/cpp-sort/detail/vergesort.h @@ -14,24 +14,82 @@ #include #include #include "bitops.h" +#include "config.h" #include "inplace_merge.h" -#include "is_sorted_until.h" #include "iterator_traits.h" +#include "lower_bound.h" #include "quick_merge_sort.h" #include "reverse.h" +#include "rotate.h" +#include "upper_bound.h" namespace cppsort { namespace detail { +namespace verge +{ + template + struct run + { + Iterator end; + difference_type_t size; + }; + template - auto vergesort(BidirectionalIterator first, BidirectionalIterator last, - difference_type_t size, - Compare compare, Projection projection, - std::bidirectional_iterator_tag) + auto merge_runs(BidirectionalIterator first, std::list>& runs, + Compare compare, Projection projection) -> void { - if (size < 80) { + if (runs.size() < 2) return; + + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + // Merge runs pairwise until there are no runs left + do { + auto begin = first; + for (auto it = runs.begin() ; it != runs.end() && it != std::prev(runs.end()) ; ++it) { + auto next_it = std::next(it); + + // Merge the runs, micro-optimize for size 1 because it can happen, + // and the generic inplace_merge algorithm cares not + if (it->size == 1) { + auto&& target = proj(*begin); + if (comp(proj(*it->end), target)) { + auto insert_it = detail::lower_bound_n(it->end, next_it->size, target, compare, projection); + detail::rotate_left(first, insert_it); + } else { + } + } else if (next_it->size == 1) { + auto&& target = proj(*std::prev(next_it->end)); + if (comp(target, proj(*std::prev(it->end)))) { + auto insert_it = detail::upper_bound_n(begin, it->size, target, compare, projection); + detail::rotate_right(insert_it, next_it->end); + } + } else { + detail::inplace_merge(begin, it->end, next_it->end, compare, projection, + it->size, next_it->size); + } + + // Compute the size of the new merged run + next_it->size += it->size; + // Remove the middle iterator to order to fuse the two + // consecutive runs, and advance to the next pair + it = runs.erase(it); + begin = it->end; + } + } while (runs.size() > 1); + } + + template + auto sort(BidirectionalIterator first, BidirectionalIterator last, + difference_type_t size, + Compare compare, Projection projection, + std::bidirectional_iterator_tag) + -> void + { + if (size < 128) { // vergesort is inefficient for small collections quick_merge_sort(std::move(first), std::move(last), size, std::move(compare), std::move(projection)); @@ -43,116 +101,191 @@ namespace detail auto&& proj = utility::as_function(projection); // Limit under which quick_merge_sort is used - int unstable_limit = size / log2(size); + int minrun_limit = size / log2(size); + + // Vergesort detects big runs in ascending or descending order, + // and remembers where each run ends by storing the end iterator + // of each run in this list, then it performs a k-way merge + std::list> runs; - // Beginning of an unstable partition, last if the - // previous partition is stable - auto begin_unstable = last; + // Beginning of an "unsorted" partition, last if the previous + // partition is sorted: as long as the algorithm does not find a + // big enough run, it "accumulates" contiguous unsorted elements, + // and sorts them all at once when it reaches the end of such a + // partition, to ensure that a minimal number of calls to the + // fallback algorithm are performed + auto begin_unsorted = last; - // Size of the unstable partition - difference_type size_unstable = 0; + // Size of the current unsorted partition + difference_type size_unsorted = 0; // Pair of iterators to iterate through the collection - auto next = is_sorted_until(first, last, compare, projection); - if (next == last) return; - auto current = std::prev(next); + auto current = first; + auto next = std::next(current); + + // Choose whether to look for an ascending or descending + // run depending on the value of the first two elements; + // when comparing equivalent there is a bias towards + // ascending runs because they don't have to be reversed + if (comp(proj(*next), proj(*current))) { + goto find_descending; + } else { + goto find_ascending; + } while (true) { - // Decreasing range - { + + // This main loop uses goto here and there, but it is written in such + // a way that gotos are not part of the hot path: there are O(log n) + // gotos performed while the loop itself is O(n). + // + // The job of this loop is to find ascending and descending runs that + // are big enough according to vergesort's heuristic. In order to do + // that we perform a linear scan with the following intuition: when we + // reach two elements whose order does not match that of the run we are + // currently trying to identify, we know that we have reached the limit + // of the current run. When it happens, there are two possible scenarios: + // - The run we found is big enough, we add it whole to the list of runs + // to merge later. We take the next two elements and compare them to + // decide whether we should start looking for an ascending or for a + // descending run next. + // - The run we found is not big enough, so we consider the last *two* + // elements to be potentially part of the next run. We then start + // looking for a run whose direction matches that of the two elements + // we just compared. + // + // The result is that the hot path it the one that constantly fails to + // find big enough runs, and it naturally performs a kind of ping-pong + // between looking for ascending and descending runs. So this loop is + // optimized for this scenario: when we start looking for a run we + // already know the order of the first two elements. However when we + // find a big enough run we only consider the last compared element to + // be part of the next run, and not the last two like we do in the hot + // path. This break the ping-pong pattern, so we use goto to jump directly + // to the run detection code corresponding to the order of the new two + // elements. + + find_ascending: { + CPPSORT_ASSERT(not comp(proj(*next), proj(*current))); + auto begin_rng = current; + ++current; + ++next; - difference_type run_size = 1; + // Find an ascending run + difference_type run_size = 2; while (next != last) { - if (comp(proj(*current), proj(*next))) break; + if (comp(proj(*next), proj(*current))) break; ++current; ++next; ++run_size; } - // Reverse and merge - if (run_size > unstable_limit) { - if (begin_unstable != last) { - quick_merge_sort(begin_unstable, begin_rng, size_unstable, compare, projection); - detail::reverse(begin_rng, next); - detail::inplace_merge(begin_unstable, begin_rng, next, compare, projection, - size_unstable, run_size); - detail::inplace_merge(first, begin_unstable, next, compare, projection, - std::distance(first, begin_unstable), size_unstable + run_size); - begin_unstable = last; - size_unstable = 0; + if (run_size > minrun_limit) { + if (begin_unsorted != last) { + quick_merge_sort(begin_unsorted, begin_rng, size_unsorted, compare, projection); + runs.push_back({ begin_rng, size_unsorted} ); + runs.push_back({ next, run_size }); + begin_unsorted = last; + size_unsorted = 0; } else { - detail::reverse(begin_rng, next); - detail::inplace_merge(first, begin_rng, next, compare, projection, - std::distance(first, begin_rng), run_size); + runs.push_back({ next, run_size }); } + if (next == last) break; + + // Find and start a new run + ++current; + ++next; + if (next == last) { + begin_unsorted = current; + size_unsorted = 0; + break; + } + if (comp(proj(*next), proj(*current))) { + goto find_descending; + } else { + goto find_ascending; + } + } else { - size_unstable += run_size; - if (begin_unstable == last) { - begin_unstable = begin_rng; + size_unsorted += (run_size - 1); + if (begin_unsorted == last) { + begin_unsorted = begin_rng; } } if (next == last) break; - - ++current; - ++next; } - // Increasing range - { + find_descending: { + CPPSORT_ASSERT(comp(proj(*next), proj(*current))); + auto begin_rng = current; + ++current; + ++next; - difference_type run_size = 1; + // Find a descending run + difference_type run_size = 2; while (next != last) { - if (comp(proj(*next), proj(*current))) break; + if (comp(proj(*current), proj(*next))) break; ++current; ++next; ++run_size; } - // Merge - if (run_size > unstable_limit) { - if (begin_unstable != last) { - quick_merge_sort(begin_unstable, begin_rng, size_unstable, compare, projection); - detail::inplace_merge(begin_unstable, begin_rng, next, compare, projection, - size_unstable, run_size); - detail::inplace_merge(first, begin_unstable, next, compare, projection, - std::distance(first, begin_unstable), size_unstable + run_size); - begin_unstable = last; - size_unstable = 0; + if (run_size > minrun_limit) { + if (begin_unsorted != last) { + quick_merge_sort(begin_unsorted, begin_rng, size_unsorted, compare, projection); + runs.push_back({ begin_rng, size_unsorted }); + detail::reverse(begin_rng, next); + runs.push_back({ next, run_size }); + begin_unsorted = last; + size_unsorted = 0; + } else { + detail::reverse(begin_rng, next); + runs.push_back({ next, run_size }); + } + + // Find and start a new run + if (next == last) break; + ++current; + ++next; + if (next == last) { + begin_unsorted = current; + size_unsorted = 0; + break; + } + if (comp(proj(*next), proj(*current))) { + goto find_descending; } else { - detail::inplace_merge(first, begin_rng, next, compare, projection, - std::distance(first, begin_rng), run_size); + goto find_ascending; } } else { - size_unstable += run_size; - if (begin_unstable == last) { - begin_unstable = begin_rng; + size_unsorted += (run_size - 1); + if (begin_unsorted == last) { + begin_unsorted = begin_rng; } } if (next == last) break; - - ++current; - ++next; } } - if (begin_unstable != last) { - quick_merge_sort(begin_unstable, last, size_unstable, compare, projection); - detail::inplace_merge(first, begin_unstable, last, - std::move(compare), std::move(projection), - std::distance(first, begin_unstable), size_unstable); + if (begin_unsorted != last) { + quick_merge_sort(begin_unsorted, last, size_unsorted, compare, projection); + runs.push_back({ last, size_unsorted + 1 }); } + + // Last step: merge the runs + verge::merge_runs(first, runs, std::move(compare), std::move(projection)); } template - auto vergesort(RandomAccessIterator first, RandomAccessIterator last, - difference_type_t size, - Compare compare, Projection projection, Fallback fallback, - std::random_access_iterator_tag) + auto sort(RandomAccessIterator first, RandomAccessIterator last, + difference_type_t size, + Compare compare, Projection projection, Fallback fallback, + std::random_access_iterator_tag) -> void { if (size < 128) { @@ -162,49 +295,54 @@ namespace detail return; } - // Limit under which pdqsort is used to sort a sub-sequence - const difference_type_t unstable_limit = size / log2(size); - - // Vergesort detects big runs in ascending or descending order, - // and remember where each run ends by storing the end iterator - // of each run in this list, then it merges everything in the end - std::list runs; - - // Beginning of an unstable partition, or last if the previous - // partition is stable - RandomAccessIterator begin_unstable = last; + // See the bidirectional overload for the description of + // the following variables + const difference_type_t minrun_limit = size / log2(size); + std::list> runs; + auto begin_unsorted = last; // Pair of iterators to iterate through the collection - RandomAccessIterator current = first; - RandomAccessIterator next = std::next(first); + auto current = first; + auto next = std::next(first); auto&& comp = utility::as_function(compare); auto&& proj = utility::as_function(projection); while (true) { + + // The random-access version of vergesort does not have to perform a linear + // scan over the whole collection like the bidirectional version does: + // instead it "jumps" n / log n elements at the same time, and start scanning + // to the left and to the right of the position to check whether it landed in + // a big enough run. In a collection that is truly shuffled, checking that + // there is no such partition in the scanning area should be really fast, and + // vergesort will perform log n jumps through the collection before falling + // back to the adapted sorting algorithm, making it a cheap preprocessing + // step. + // Beginning of the current sequence - RandomAccessIterator begin_range = current; + auto begin_range = current; - // If the last part of the collection to sort isn't - // big enough, consider that it is an unstable sequence - if (last - next <= unstable_limit) { - if (begin_unstable == last) { - begin_unstable = begin_range; + // If the last part of the collection to sort is not + // big enough, consider that it is an unsorted sequence + if (last - next <= minrun_limit) { + if (begin_unsorted == last) { + begin_unsorted = begin_range; } break; } // Set backward iterators - current += unstable_limit; - next += unstable_limit; + current += minrun_limit; + next += minrun_limit; // Set forward iterators - RandomAccessIterator current2 = current; - RandomAccessIterator next2 = next; + auto current2 = current; + auto next2 = next; if (comp(proj(*next), proj(*current))) { - // Found a decreasing sequence, move iterators - // to the limits of the sequence + // Found an increasing run, scan to the left and to the right + // until the limits of the run are reached do { --current; --next; @@ -221,26 +359,26 @@ namespace detail } // Check whether we found a big enough sorted sequence - if (next2 - current >= unstable_limit) { + if (next2 - current >= minrun_limit) { detail::reverse(current, next2); - if ((current - begin_range) && begin_unstable == last) { - begin_unstable = begin_range; + if ((current - begin_range) && begin_unsorted == last) { + begin_unsorted = begin_range; } - if (begin_unstable != last) { - fallback(begin_unstable, current, compare, projection); - runs.push_back(current); - begin_unstable = last; + if (begin_unsorted != last) { + fallback(begin_unsorted, current, compare, projection); + runs.push_back({ current, current - begin_unsorted }); + begin_unsorted = last; } - runs.push_back(next2); + runs.push_back({ next2, next2 - current }); } else { // Remember the beginning of the unsorted sequence - if (begin_unstable == last) { - begin_unstable = begin_range; + if (begin_unsorted == last) { + begin_unsorted = begin_range; } } } else { - // Found an increasing sequence, move iterators - // to the limits of the sequence + // Found an increasing run, scan to the left and to the right + // until the limits of the run are reached do { --current; --next; @@ -257,20 +395,20 @@ namespace detail } // Check whether we found a big enough sorted sequence - if (next2 - current >= unstable_limit) { - if ((current - begin_range) && begin_unstable == last) { - begin_unstable = begin_range; + if (next2 - current >= minrun_limit) { + if ((current - begin_range) && begin_unsorted == last) { + begin_unsorted = begin_range; } - if (begin_unstable != last) { - fallback(begin_unstable, current, compare, projection); - runs.push_back(current); - begin_unstable = last; + if (begin_unsorted != last) { + fallback(begin_unsorted, current, compare, projection); + runs.push_back({ current, current - begin_unsorted }); + begin_unsorted = last; } - runs.push_back(next2); + runs.push_back({ next2, next2 - current }); } else { // Remember the beginning of the unsorted sequence - if (begin_unstable == last) { - begin_unstable = begin_range; + if (begin_unsorted == last) { + begin_unsorted = begin_range; } } } @@ -281,62 +419,52 @@ namespace detail next = std::next(next2); } - if (begin_unstable != last) { + if (begin_unsorted != last) { // If there are unsorted elements left, sort them - runs.push_back(last); - fallback(begin_unstable, last, compare, projection); + fallback(begin_unsorted, last, compare, projection); + runs.push_back({ last, last - begin_unsorted }); } - if (runs.size() < 2) return; - - // Merge runs pairwise until there aren't runs left - do { - auto begin = first; - for (auto it = runs.begin() ; it != runs.end() && it != std::prev(runs.end()) ; ++it) { - detail::inplace_merge(begin, *it, *std::next(it), compare, projection); - // Remove the middle iterator and advance - it = runs.erase(it); - begin = *it; - } - } while (runs.size() > 1); + // Last step: merge the runs + verge::merge_runs(first, runs, std::move(compare), std::move(projection)); } template - auto vergesort(RandomAccessIterator first, RandomAccessIterator last, - difference_type_t size, - Compare compare, Projection projection, - std::random_access_iterator_tag category) + auto sort(RandomAccessIterator first, RandomAccessIterator last, + difference_type_t size, + Compare compare, Projection projection, + std::random_access_iterator_tag category) -> void { using sorter = cppsort::pdq_sorter; - vergesort(std::move(first), std::move(last), size, - std::move(compare), std::move(projection), - sorter{}, category); + verge::sort(std::move(first), std::move(last), size, + std::move(compare), std::move(projection), + sorter{}, category); } template - auto vergesort(BidirectionalIterator first, BidirectionalIterator last, - difference_type_t size, - Compare compare, Projection projection, Fallback fallback) + auto sort(BidirectionalIterator first, BidirectionalIterator last, + difference_type_t size, + Compare compare, Projection projection, Fallback fallback) -> void { - vergesort(std::move(first), std::move(last), size, - std::move(compare), std::move(projection), - std::move(fallback), std::random_access_iterator_tag{}); + verge::sort(std::move(first), std::move(last), size, + std::move(compare), std::move(projection), + std::move(fallback), std::random_access_iterator_tag{}); } template - auto vergesort(BidirectionalIterator first, BidirectionalIterator last, - difference_type_t size, - Compare compare, Projection projection) + auto sort(BidirectionalIterator first, BidirectionalIterator last, + difference_type_t size, + Compare compare, Projection projection) -> void { using category = iterator_category_t; - vergesort(std::move(first), std::move(last), size, - std::move(compare), std::move(projection), - category{}); + verge::sort(std::move(first), std::move(last), size, + std::move(compare), std::move(projection), + category{}); } -}} +}}} #endif // CPPSORT_DETAIL_VERGESORT_H_ diff --git a/include/cpp-sort/sorters/verge_sorter.h b/include/cpp-sort/sorters/verge_sorter.h index 44d86e6e..1af162cb 100644 --- a/include/cpp-sort/sorters/verge_sorter.h +++ b/include/cpp-sort/sorters/verge_sorter.h @@ -49,9 +49,9 @@ namespace cppsort "verge_sorter requires at least bidirectional iterators" ); - vergesort(std::begin(iterable), std::end(iterable), - utility::size(iterable), - std::move(compare), std::move(projection)); + verge::sort(std::begin(iterable), std::end(iterable), + utility::size(iterable), + std::move(compare), std::move(projection)); } template< @@ -75,8 +75,8 @@ namespace cppsort ); auto size = std::distance(first, last); - vergesort(std::move(first), std::move(last), size, - std::move(compare), std::move(projection)); + verge::sort(std::move(first), std::move(last), size, + std::move(compare), std::move(projection)); } //////////////////////////////////////////////////////////// From 0b634cce0e17af2e66231e0ca2a7aaa6fb1a5822 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 23 Nov 2020 09:26:40 +0100 Subject: [PATCH 12/90] Improvements to inplace_merge overloads that don't take the sizes - The forward iterator version used utility::as_function without properly qualifying it, which was a bug. - The bidirectional version was updated with the "shrink on the left" trick, which can both decrease the cost of the size computation and reduce the number of elements to allocate. --- include/cpp-sort/detail/inplace_merge.h | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/include/cpp-sort/detail/inplace_merge.h b/include/cpp-sort/detail/inplace_merge.h index ad6eac95..8c4d86b6 100644 --- a/include/cpp-sort/detail/inplace_merge.h +++ b/include/cpp-sort/detail/inplace_merge.h @@ -93,14 +93,14 @@ namespace detail -> void { using rvalue_reference = remove_cvref_t>; - auto&& comp = as_function(compare); - auto&& proj = as_function(projection); + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); - // Shrink the problem size on the left side + // Shrink the problem size on the left side, makes the + // size computation potentially cheaper while (first != middle && not comp(proj(*middle), proj(*first))) { ++first; } - if (first == middle) return; auto n0 = std::distance(first, middle); @@ -138,12 +138,21 @@ namespace detail -> void { using rvalue_reference = remove_cvref_t>; + using category = iterator_category_t; + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + // Shrink the problem size on the left side, makes the + // size computation potentially cheaper + while (first != middle && not comp(proj(*middle), proj(*first))) { + ++first; + } + if (first == middle) return; auto len1 = std::distance(first, middle); auto len2 = std::distance(middle, last); - temporary_buffer buffer(std::min(len1, len2)); - using category = iterator_category_t; + temporary_buffer buffer(std::min(len1, len2)); inplace_merge(std::move(first), std::move(middle), std::move(last), std::move(compare), std::move(projection), len1, len2, buffer.data(), buffer.size(), From a9c55e9accb3200e859da6854eef4fa997126531 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 23 Nov 2020 10:43:31 +0100 Subject: [PATCH 13/90] QuickMergeSort -> QuickMergesort [ci skip] --- docs/Original-research.md | 4 ++-- docs/Sorters.md | 6 +++--- include/cpp-sort/detail/quick_merge_sort.h | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/Original-research.md b/docs/Original-research.md index ace9ab7a..6fb3ceac 100644 --- a/docs/Original-research.md +++ b/docs/Original-research.md @@ -6,10 +6,10 @@ You can find some experiments and interesting pieces of code [in my Gist][morwen One of the main observations which naturally occured as long as I was putting together this library was about the best complexity tradeoffs between time and memory depending on the iterator categories of the different sorting algorithms (only taking comparison sorts into account): * Algorithms that work on random-access iterators can run in O(n log n) time with O(1) extra memory, and can even be stable with such guarantees (block sort being the best example). -* Unstable algorithms that work on bidirectional iterators can run in O(n log n) time with O(1) extra memory: QuickMergeSort [can be implemented][https://github.com/Morwenn/quick_merge_sort] with a bottom-up mergesort and a raw median-of-medians algorithms (instead of the introselect mutual recursion). +* Unstable algorithms that work on bidirectional iterators can run in O(n log n) time with O(1) extra memory: QuickMergesort [can be implemented][https://github.com/Morwenn/quick_merge_sort] with a bottom-up mergesort and a raw median-of-medians algorithms (instead of the introselect mutual recursion). * Stable algorithms that work on bidirectional iterators can run in O(n log n) time with O(n) extra memory (mergesort), or in O(n log² n) time with O(1) extra memory (mergesort with in-place merge). * Stable algorithms that work on forward iterators can get down to the same time and memory complexities than the the ones working on bidirectional iterators: mergesort works just as well. -* Unstable algorithms that work on forward iterators can run in O(n log² n) time and O(1) space, QuickMergeSort being once again the prime example of such an algorithm. +* Unstable algorithms that work on forward iterators can run in O(n log² n) time and O(1) space, QuickMergesort being once again the prime example of such an algorithm. * Taking advantage of the list data structure allows for sorting algorithms running in O(n log n) time with O(1) extra memory, be it for stable sorting (mergesort) or unstable sorting (melsort), but those techniques can't be generically retrofitted to generically work with bidirectional iterators Now, those observations/claims are there to be challenged: if you know of any stable comparison sorting algorithm that runs on bidirectional iterators in O(n log n) with O(1) extra memory, don't hesitate to be the ones challenging those claims :) diff --git a/docs/Sorters.md b/docs/Sorters.md index dd0a9234..47641c83 100644 --- a/docs/Sorters.md +++ b/docs/Sorters.md @@ -223,7 +223,7 @@ This sorter is a bit faster or a bit slower than `smooth_sorter` depending on th #include ``` -Implements a flavour of [QuickMergeSort](https://arxiv.org/abs/1307.3033). +Implements a flavour of [QuickMergesort](https://arxiv.org/abs/1307.3033). | Best | Average | Worst | Memory | Stable | Iterators | | ----------- | ----------- | ----------- | ----------- | ----------- | ------------- | @@ -231,7 +231,7 @@ Implements a flavour of [QuickMergeSort](https://arxiv.org/abs/1307.3033). | n | n log n | n log n | log² n | No | Bidirectional | | n | n log² n | n log² n | log² n | No | Forward | -QuickMergeSort is an algorithm that performs a quicksort-like partition and tries to use mergesort on the bigger partition, using the smaller one as a swap buffer used for the merge operation when possible. The flavour of QuickMergeSort used by `quick_merge_sorter` actually uses an equivalent of [`std::nth_element`](https://en.cppreference.com/w/cpp/algorithm/nth_element) to partition the collection in 1/3-2/3 parts in order to maximize the size of the partition (2 thirds of the space) that can be merge-sorted using the other partition as a swap buffer. +QuickMergesort is an algorithm that performs a quicksort-like partition and tries to use mergesort on the bigger partition, using the smaller one as a swap buffer used for the merge operation when possible. The flavour of QuickMergesort used by `quick_merge_sorter` actually uses an equivalent of [`std::nth_element`](https://en.cppreference.com/w/cpp/algorithm/nth_element) to partition the collection in 1/3-2/3 parts in order to maximize the size of the partition (2 thirds of the space) that can be merge-sorted using the other partition as a swap buffer. The change in time complexity for forward iterators is due to the partitioning algorithm being O(n log n) instead of O(n). The log n memory is due to top-down mergesort stack recursion in the random-access version, while the memory of the forward version use is dominated by the mutually recursive [introselect](https://en.wikipedia.org/wiki/Introselect) algorithm which is used to implement an `nth_element` equivalent for forward iterators. @@ -382,7 +382,7 @@ Implements a [vergesort](https://github.com/Morwenn/vergesort) algorithm backed | n | n log n | n log n log log n | n | No | Bidirectional | | n | n log n | n log n | log² n | No | Bidirectional | -Vergesort is a [*Runs*-adaptive](https://github.com/Morwenn/cpp-sort/wiki/Measures-of-presortedness#runs) algorithm (including descending runs) as long as the size of those runs is greater than *n / log n*; when the runs are smaller, it falls back to another sorting algorithm to sort them (pdqsort for random-access iterators, QuickMergeSort otherwise). +Vergesort is a [*Runs*-adaptive](https://github.com/Morwenn/cpp-sort/wiki/Measures-of-presortedness#runs) algorithm (including descending runs) as long as the size of those runs is greater than *n / log n*; when the runs are smaller, it falls back to another sorting algorithm to sort them (pdqsort for random-access iterators, QuickMergesort otherwise). Vergesort's complexity is bound either by its optimization layer or by the fallback sorter's complexity: * When it doesn't find big runs, the complexity is bound by the fallback sorter: depending on the category of iterators you can refer to the tables of either `pdq_sorter` or `quick_merge_sorter`. diff --git a/include/cpp-sort/detail/quick_merge_sort.h b/include/cpp-sort/detail/quick_merge_sort.h index 176c4f69..9371515d 100644 --- a/include/cpp-sort/detail/quick_merge_sort.h +++ b/include/cpp-sort/detail/quick_merge_sort.h @@ -127,9 +127,9 @@ namespace detail Compare compare, Projection projection) -> void { - // This flavour of QuickMergeSort splits the collection in [2/3, 1/3] + // This flavour of QuickMergesort splits the collection in [2/3, 1/3] // partitions where the right partition is used as an internal buffer - // to apply mergesort to the left partition, then QuickMergeSort is + // to apply mergesort to the left partition, then QuickMergesort is // recursively applied to the smaller right partition while (size > qmsort_limit) { From a030e319d671443533d639ebaa0d4b0da5da260d Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 23 Nov 2020 16:58:01 +0100 Subject: [PATCH 14/90] Move code coverage to GitHub Actions (#176) First step to move the build to GitHub Actions: move the coverage generation from Travis to Actions. It doesn't generate additional information with lcov anymore, and now always runs on the latest available Ubuntu. [ci skip] --- .github/workflows/codecov.yml | 52 +++++++++++++++++++++++++++++++++++ .travis.yml | 23 ---------------- 2 files changed, 52 insertions(+), 23 deletions(-) create mode 100644 .github/workflows/codecov.yml diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml new file mode 100644 index 00000000..813dba89 --- /dev/null +++ b/.github/workflows/codecov.yml @@ -0,0 +1,52 @@ +name: Coverage Upload to Codecov + +on: + push: + branches: + - coverage-action + - master + - develop + - 2.0.0-develop + paths: + - 'include/**' + - 'testsuite/**' + +env: + BUILD_TYPE: Debug + +jobs: + upload-coverage: + runs-on: ubuntu-latest + + steps: + - name: Checkout project + uses: actions/checkout@v2 + + - name: Configure CMake + shell: bash + working-directory: ${{runner.workspace}} + run: > + cmake -H${{github.event.repository.name}} -Bbuild + -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" + -DENABLE_COVERAGE=true + -G"Unix Makefiles" + + - name: Build with coverage + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake --build . --config $BUILD_TYPE -j 2 + + - name: Run the test suite + shell: bash + working-directory: ${{runner.workspace}}/build + run: ctest -C Release --output-on-failure + + - name: Create coverage info + shell: bash + working-directory: ${{runner.workspace}}/build + run: make gcov + + - name: Upload coverage info + shell: bash + working-directory: ${{runner.workspace}}/build + run: bash <(curl -s https://codecov.io/bash) -X gcov || echo "Codecov did not collect coverage reports"; diff --git a/.travis.yml b/.travis.yml index 72c1689a..7d7428c7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -146,19 +146,6 @@ matrix: env: BUILD_TYPE=Release CMAKE_GENERATOR="MinGW Makefiles" compiler: gcc - # Code coverage - - os: linux - sudo: false - if: branch in (master, develop) - env: BUILD_TYPE=Debug CMAKE_GENERATOR="Unix Makefiles" ENABLE_COVERAGE=true - addons: - apt: - <<: *apt-common - packages: - - *gcc - - lcov - compiler: gcc - before_install: - if [[ $TRAVIS_OS_NAME = "linux" && $CXX = "clang++" ]]; then sudo ln -s $(which ccache) /usr/lib/ccache/clang++; @@ -199,16 +186,6 @@ script: cd ..; fi -after_success: - - if [[ $TRAVIS_OS_NAME = "windows" ]]; then - cd build; - fi - - if [[ $ENABLE_COVERAGE = true ]]; then - make gcov; - make lcov; - bash <(curl -s https://codecov.io/bash) -X gcov || echo "Codecov did not collect coverage reports"; - fi - after_failure: - if [[ $TRAVIS_OS_NAME = "windows" ]]; then cd build; From f1f34de9d0afab52ea78ad5e1d5e5d28bb492f2b Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 24 Nov 2020 10:25:34 +0100 Subject: [PATCH 15/90] errorbar-plot: fix MatplotlibDeprecationWarning [ci skip] --- benchmarks/errorbar-plot/plot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks/errorbar-plot/plot.py b/benchmarks/errorbar-plot/plot.py index a0af7170..90012710 100644 --- a/benchmarks/errorbar-plot/plot.py +++ b/benchmarks/errorbar-plot/plot.py @@ -59,7 +59,7 @@ def main(): ax.grid(True) ax.set_xlabel('Size') ax.set_ylabel('Time [s]') - ax.set_xscale('log', basex=2) + ax.set_xscale('log', base=2) ax.set_yscale('log') pyplot.title("Sorting std::vector") From fdcd2318c5761128257907c190693cf88958fa57 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 24 Nov 2020 12:24:27 +0100 Subject: [PATCH 16/90] Unwrap nested stable_adapter It is totally possible for nested wrapping of stable_adapter to happen, this commit makes stable_adapter> equivalent to stable_adapter. --- include/cpp-sort/adapters/stable_adapter.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/cpp-sort/adapters/stable_adapter.h b/include/cpp-sort/adapters/stable_adapter.h index d84fc827..707e795a 100644 --- a/include/cpp-sort/adapters/stable_adapter.h +++ b/include/cpp-sort/adapters/stable_adapter.h @@ -241,6 +241,12 @@ namespace cppsort using is_always_stable = std::true_type; }; + + // Accidental nesting can happen, unwrap + template + struct stable_adapter>: + stable_adapter + {}; } #ifdef CPPSORT_ADAPTERS_HYBRID_ADAPTER_DONE_ From 9970afed1d37014718a38e93b510f73503618159 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 24 Nov 2020 15:09:27 +0100 Subject: [PATCH 17/90] Try to make Codecov find the project's codecov.yml [ci skip] --- .github/workflows/codecov.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 813dba89..e9417cf1 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -1,3 +1,6 @@ +# Copyright (c) 2020 Morwenn +# SPDX-License-Identifier: MIT + name: Coverage Upload to Codecov on: @@ -48,5 +51,5 @@ jobs: - name: Upload coverage info shell: bash - working-directory: ${{runner.workspace}}/build + working-directory: ${{runner.workspace}} run: bash <(curl -s https://codecov.io/bash) -X gcov || echo "Codecov did not collect coverage reports"; From 33be4397dcf6ccf0dac5fab89dfd4841c496168f Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 26 Nov 2020 14:45:43 +0100 Subject: [PATCH 18/90] Specialize is_probably_branchless_projection for std::mem_fn (#177) Declare that the result of std::mem_fn is branchless when it wraps a pointer to data member. This is part of a wide library audit to make pdqsort use its branchless partitioning algorithm more often. This specific change uses standard library implementation-specific class names, but it is quite important since as_function currently uses std::mem_fn where it makes sense. --- include/cpp-sort/utility/branchless_traits.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/include/cpp-sort/utility/branchless_traits.h b/include/cpp-sort/utility/branchless_traits.h index 5ec0e38e..2f1cc9c7 100644 --- a/include/cpp-sort/utility/branchless_traits.h +++ b/include/cpp-sort/utility/branchless_traits.h @@ -99,6 +99,18 @@ namespace utility std::is_member_object_pointer {}; +#if defined(__GLIBCXX__) +template +struct is_probably_branchless_projection_impl, U>: + std::is_member_object_pointer +{}; +#elif defined(_LIBCPP_VERSION) + template + struct is_probably_branchless_projection_impl, U>: + std::is_member_object_pointer + {}; +#endif + #if CPPSORT_STD_IDENTITY_AVAILABLE template struct is_probably_branchless_projection_impl: From 286ac28000adf25ae6ce7ef88890ac3c90988ea4 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 26 Nov 2020 23:21:48 +0100 Subject: [PATCH 19/90] Get the new Codecov GitHub Actions workflow to work (#176) The following modifications were needed: - Use the GitHub Action codecov/codecov-action - Rename the workflow from codecov.yml to code-coverage.yml, otherwise the Codecov Action things it is the Codecov configuration file - Change codecov.yml to ensure that the test files are not covered [ci skip] --- .github/workflows/codecov.yml | 7 +++---- codecov.yml | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index e9417cf1..ce5fbddd 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -6,7 +6,6 @@ name: Coverage Upload to Codecov on: push: branches: - - coverage-action - master - develop - 2.0.0-develop @@ -50,6 +49,6 @@ jobs: run: make gcov - name: Upload coverage info - shell: bash - working-directory: ${{runner.workspace}} - run: bash <(curl -s https://codecov.io/bash) -X gcov || echo "Codecov did not collect coverage reports"; + uses: codecov/codecov-action@v1.0.15 + with: + directory: ${{runner.workspace}}/build diff --git a/codecov.yml b/codecov.yml index ef8acb94..84fab2a6 100644 --- a/codecov.yml +++ b/codecov.yml @@ -3,7 +3,7 @@ coverage: ignore: - - "testsuite/**/*" + - "testsuite" # This unrolled version of a merge-insertion sort derivative was tested # exhaustively for every permutation of an integer sequence of 9 elements, # thus we don't include it From b3522dd637a96330b513f6fd490155c41996bffa Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 28 Nov 2020 20:04:17 +0100 Subject: [PATCH 20/90] Refactor vergesort a bit to make future evolutions easier --- include/cpp-sort/detail/vergesort.h | 75 +++++++++++++++++++---------- 1 file changed, 50 insertions(+), 25 deletions(-) diff --git a/include/cpp-sort/detail/vergesort.h b/include/cpp-sort/detail/vergesort.h index 408a17fa..f7f96959 100644 --- a/include/cpp-sort/detail/vergesort.h +++ b/include/cpp-sort/detail/vergesort.h @@ -29,6 +29,10 @@ namespace detail { namespace verge { + //////////////////////////////////////////////////////////// + // Run helper class, represents a non-decreasing run with + // its size and end iterator + template struct run { @@ -36,6 +40,9 @@ namespace verge difference_type_t size; }; + //////////////////////////////////////////////////////////// + // Merge a list of runs with a k-way merge + template auto merge_runs(BidirectionalIterator first, std::list>& runs, Compare compare, Projection projection) @@ -82,11 +89,13 @@ namespace verge } while (runs.size() > 1); } + //////////////////////////////////////////////////////////// + // Vergesort main algorithms + template - auto sort(BidirectionalIterator first, BidirectionalIterator last, - difference_type_t size, - Compare compare, Projection projection, - std::bidirectional_iterator_tag) + auto sort_bidirectional(BidirectionalIterator first, BidirectionalIterator last, + difference_type_t size, + Compare compare, Projection projection) -> void { if (size < 128) { @@ -280,12 +289,10 @@ namespace verge verge::merge_runs(first, runs, std::move(compare), std::move(projection)); } - template - auto sort(RandomAccessIterator first, RandomAccessIterator last, - difference_type_t size, - Compare compare, Projection projection, Fallback fallback, - std::random_access_iterator_tag) + template + auto sort_random_access(RandomAccessIterator first, RandomAccessIterator last, + difference_type_t size, + Compare compare, Projection projection, Fallback fallback) -> void { if (size < 128) { @@ -341,7 +348,7 @@ namespace verge auto next2 = next; if (comp(proj(*next), proj(*current))) { - // Found an increasing run, scan to the left and to the right + // Found a decreasing run, scan to the left and to the right // until the limits of the run are reached do { --current; @@ -429,29 +436,48 @@ namespace verge verge::merge_runs(first, runs, std::move(compare), std::move(projection)); } - template - auto sort(RandomAccessIterator first, RandomAccessIterator last, + //////////////////////////////////////////////////////////// + // Vergesort main interface + + template + auto sort(std::bidirectional_iterator_tag, + BidirectionalIterator first, BidirectionalIterator last, + difference_type_t size, + Compare compare, Projection projection, Fallback /* fallback */) + -> void + { + // This overload exists purely for the sake of genericity + // and to make future evolutions easier, it is actually + // never called with anything else than pdq_sorter as a + // fallback for now, which can't even be used by the + // bidirectional algorithm + sort_bidirectional(std::move(first), std::move(last), size, + std::move(compare), std::move(projection)); + } + + template + auto sort(std::random_access_iterator_tag, + RandomAccessIterator first, RandomAccessIterator last, difference_type_t size, - Compare compare, Projection projection, - std::random_access_iterator_tag category) + Compare compare, Projection projection, Fallback fallback) -> void { - using sorter = cppsort::pdq_sorter; - verge::sort(std::move(first), std::move(last), size, - std::move(compare), std::move(projection), - sorter{}, category); + // Forward every parameter as is + sort_random_access(std::move(first), std::move(last), size, + std::move(compare), std::move(projection), + std::move(fallback)); } - template + template auto sort(BidirectionalIterator first, BidirectionalIterator last, difference_type_t size, Compare compare, Projection projection, Fallback fallback) -> void { - verge::sort(std::move(first), std::move(last), size, + verge::sort(iterator_category_t{}, + std::move(first), std::move(last), size, std::move(compare), std::move(projection), - std::move(fallback), std::random_access_iterator_tag{}); + std::move(fallback)); } template @@ -460,10 +486,9 @@ namespace verge Compare compare, Projection projection) -> void { - using category = iterator_category_t; verge::sort(std::move(first), std::move(last), size, std::move(compare), std::move(projection), - category{}); + cppsort::pdq_sorter{}); } }}} From c4dace8e9129ac1e9118e928b609be042d686f0d Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 30 Nov 2020 16:00:02 +0100 Subject: [PATCH 21/90] Put all the test suite wrappers in a single file --- testsuite/adapters/counting_adapter.cpp | 7 ++- testsuite/adapters/mixed_adapters.cpp | 3 +- .../schwartz_adapter_every_sorter.cpp | 15 +++--- ...schwartz_adapter_every_sorter_reversed.cpp | 15 +++--- .../schwartz_adapter_fixed_sorters.cpp | 7 ++- testsuite/adapters/small_array_adapter.cpp | 3 +- .../adapters/stable_adapter_every_sorter.cpp | 22 +------- testsuite/sorter_facade.cpp | 3 +- testsuite/sorter_facade_iterable.cpp | 3 +- .../sorters/default_sorter_projection.cpp | 5 +- .../merge_insertion_sorter_projection.cpp | 3 +- testsuite/sorters/merge_sorter_projection.cpp | 3 +- testsuite/sorters/ska_sorter_projection.cpp | 3 +- .../sorters/spread_sorter_projection.cpp | 5 +- testsuite/sorters/std_sorter.cpp | 3 +- testsuite/testing-tools/wrapper.h | 54 +++++++++++++++++++ testsuite/utility/chainable_projections.cpp | 5 +- 17 files changed, 99 insertions(+), 60 deletions(-) create mode 100644 testsuite/testing-tools/wrapper.h diff --git a/testsuite/adapters/counting_adapter.cpp b/testsuite/adapters/counting_adapter.cpp index 4d883046..ded8fabd 100644 --- a/testsuite/adapters/counting_adapter.cpp +++ b/testsuite/adapters/counting_adapter.cpp @@ -16,6 +16,9 @@ #include #include #include +#include + +using wrapper = generic_wrapper; TEST_CASE( "basic counting_adapter tests", "[counting_adapter][selection_sorter]" ) @@ -42,8 +45,6 @@ TEST_CASE( "basic counting_adapter tests", SECTION( "with projections" ) { - struct wrapper { int value; }; - // Pseudo-random number engine std::mt19937_64 engine(Catch::rngSeed()); @@ -103,8 +104,6 @@ TEST_CASE( "counting_adapter with span", SECTION( "with projections" ) { - struct wrapper { int value; }; - // Pseudo-random number engine std::mt19937_64 engine(Catch::rngSeed()); diff --git a/testsuite/adapters/mixed_adapters.cpp b/testsuite/adapters/mixed_adapters.cpp index b1003bdc..bd09fd59 100644 --- a/testsuite/adapters/mixed_adapters.cpp +++ b/testsuite/adapters/mixed_adapters.cpp @@ -25,6 +25,7 @@ #include #include #include +#include namespace { @@ -95,7 +96,7 @@ TEST_CASE( "hybrid_adapter over adapters that change iterator category", TEST_CASE( "indirect sort with Schwartzian transform", "[indirect_adapter][schwartz_adapter]" ) { - struct wrapper { short value; }; + using wrapper = generic_wrapper; std::vector collection(334); helpers::iota(std::begin(collection), std::end(collection), -93, &wrapper::value); diff --git a/testsuite/adapters/schwartz_adapter_every_sorter.cpp b/testsuite/adapters/schwartz_adapter_every_sorter.cpp index 1d587767..b28ad811 100644 --- a/testsuite/adapters/schwartz_adapter_every_sorter.cpp +++ b/testsuite/adapters/schwartz_adapter_every_sorter.cpp @@ -14,16 +14,13 @@ #include #include #include +#include -namespace -{ - // double segfaults with MinGW-w64 64 bits in Release mode - template - struct wrapper - { - T value; - }; -} +// NOTE: this test used to use wrapper, but it was later +// switched to wrapper because the former segfaults +// with MinGW-w64 64 bits in Release mode +template +using wrapper = generic_wrapper; TEMPLATE_TEST_CASE( "every random-access sorter with Schwartzian transform adapter", "[schwartz_adapter]", cppsort::block_sorter>, diff --git a/testsuite/adapters/schwartz_adapter_every_sorter_reversed.cpp b/testsuite/adapters/schwartz_adapter_every_sorter_reversed.cpp index 11946254..fd9e9b2a 100644 --- a/testsuite/adapters/schwartz_adapter_every_sorter_reversed.cpp +++ b/testsuite/adapters/schwartz_adapter_every_sorter_reversed.cpp @@ -12,16 +12,13 @@ #include #include #include +#include -namespace -{ - // double segfaults with MinGW-w64 64 bits in Release mode - template - struct wrapper - { - T value; - }; -} +// NOTE: this test used to use wrapper, but it was later +// switched to wrapper because the former segfaults +// with MinGW-w64 64 bits in Release mode +template +using wrapper = generic_wrapper; TEMPLATE_TEST_CASE( "every sorter with Schwartzian transform adapter and reverse iterators", "[schwartz_adapter][reverse_iterator]", diff --git a/testsuite/adapters/schwartz_adapter_fixed_sorters.cpp b/testsuite/adapters/schwartz_adapter_fixed_sorters.cpp index b14f2712..acef6084 100644 --- a/testsuite/adapters/schwartz_adapter_fixed_sorters.cpp +++ b/testsuite/adapters/schwartz_adapter_fixed_sorters.cpp @@ -11,12 +11,13 @@ #include #include #include +#include + +using wrapper = generic_wrapper; TEST_CASE( "Schwartzian transform adapter with fixed-size sorters", "[schwartz_adapter]" ) { - struct wrapper { double value; }; - auto&& low_comparisons_sort = cppsort::schwartz_adapter< cppsort::small_array_adapter< cppsort::low_comparisons_sorter @@ -162,8 +163,6 @@ TEST_CASE( "Schwartzian transform adapter with fixed-size sorters", TEST_CASE( "stability of Schwartzian transform adapter with fixed-size sorters", "[schwartz_adapter][is_stable]" ) { - struct wrapper { double value; }; - using namespace cppsort; using sorter = schwartz_adapter< small_array_adapter< diff --git a/testsuite/adapters/small_array_adapter.cpp b/testsuite/adapters/small_array_adapter.cpp index 1ff78616..fa748721 100644 --- a/testsuite/adapters/small_array_adapter.cpp +++ b/testsuite/adapters/small_array_adapter.cpp @@ -14,6 +14,7 @@ #include #include #include +#include namespace { @@ -174,7 +175,7 @@ TEST_CASE( "small array adapter", SECTION( "with domain and projections" ) { - struct wrapper { int value; }; + using wrapper = generic_wrapper; std::array collection; cppsort::small_array_adapter< diff --git a/testsuite/adapters/stable_adapter_every_sorter.cpp b/testsuite/adapters/stable_adapter_every_sorter.cpp index d663d8ed..63260917 100644 --- a/testsuite/adapters/stable_adapter_every_sorter.cpp +++ b/testsuite/adapters/stable_adapter_every_sorter.cpp @@ -13,27 +13,9 @@ #include #include #include +#include -namespace -{ - struct wrapper - { - int value; - int order; - }; - - auto operator<(const wrapper& lhs, const wrapper& rhs) - -> bool - { - if (lhs.value < rhs.value) { - return true; - } - if (rhs.value < lhs.value) { - return false; - } - return lhs.order < rhs.order; - } -} +using wrapper = generic_stable_wrapper; TEMPLATE_TEST_CASE( "every random-access sorter with stable_adapter", "[stable_adapter]", cppsort::block_sorter>, diff --git a/testsuite/sorter_facade.cpp b/testsuite/sorter_facade.cpp index 97f557a3..c75649a2 100644 --- a/testsuite/sorter_facade.cpp +++ b/testsuite/sorter_facade.cpp @@ -10,6 +10,7 @@ #include #include #include +#include namespace { @@ -69,7 +70,7 @@ TEST_CASE( "sorter_facade miscellaneous checks", // Some checks to make sure that sorter_facade always // forwards the value correctly in the most common cases - struct wrapper { int value; }; + using wrapper = generic_wrapper; // Collection to "sort" std::vector vec; diff --git a/testsuite/sorter_facade_iterable.cpp b/testsuite/sorter_facade_iterable.cpp index c56fad0c..02aa23e1 100644 --- a/testsuite/sorter_facade_iterable.cpp +++ b/testsuite/sorter_facade_iterable.cpp @@ -10,6 +10,7 @@ #include #include #include +#include namespace { @@ -105,7 +106,7 @@ TEST_CASE( "sorter_facade with sorters overloaded for iterables", // of a pair of iterators. We need to make sure that these // optimizations work too. - struct wrapper { int value; }; + using wrapper = generic_wrapper; // Collection to "sort" std::vector vec; diff --git a/testsuite/sorters/default_sorter_projection.cpp b/testsuite/sorters/default_sorter_projection.cpp index 723e6260..2bbc0629 100644 --- a/testsuite/sorters/default_sorter_projection.cpp +++ b/testsuite/sorters/default_sorter_projection.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2018 Morwenn + * Copyright (c) 2015-2020 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -13,6 +13,7 @@ #include #include #include +#include TEST_CASE( "default sorter tests with projections", "[default_sorter][projection]" ) @@ -21,7 +22,7 @@ TEST_CASE( "default sorter tests with projections", std::mt19937_64 engine(Catch::rngSeed()); // Wrapper to hide the integer - struct wrapper { int value; }; + using wrapper = generic_wrapper; // Collection to sort std::vector vec(80); diff --git a/testsuite/sorters/merge_insertion_sorter_projection.cpp b/testsuite/sorters/merge_insertion_sorter_projection.cpp index 587c55cd..76dea3d3 100644 --- a/testsuite/sorters/merge_insertion_sorter_projection.cpp +++ b/testsuite/sorters/merge_insertion_sorter_projection.cpp @@ -10,6 +10,7 @@ #include #include #include +#include TEST_CASE( "merge_insertion_sorter tests with projections", "[merge_insertion_sorter][projection]" ) @@ -18,7 +19,7 @@ TEST_CASE( "merge_insertion_sorter tests with projections", std::mt19937_64 engine(Catch::rngSeed()); // Wrapper to hide the integer - struct wrapper { int value; }; + using wrapper = generic_wrapper; // Collection to sort std::vector vec(80); diff --git a/testsuite/sorters/merge_sorter_projection.cpp b/testsuite/sorters/merge_sorter_projection.cpp index 89260c4e..0331cb8f 100644 --- a/testsuite/sorters/merge_sorter_projection.cpp +++ b/testsuite/sorters/merge_sorter_projection.cpp @@ -12,6 +12,7 @@ #include #include #include +#include TEST_CASE( "merge_sorter tests with projections", "[merge_sorter][projection]" ) @@ -20,7 +21,7 @@ TEST_CASE( "merge_sorter tests with projections", std::mt19937_64 engine(Catch::rngSeed()); // Wrapper to hide the integer - struct wrapper { int value; }; + using wrapper = generic_wrapper; // Collection to sort std::vector vec(80); diff --git a/testsuite/sorters/ska_sorter_projection.cpp b/testsuite/sorters/ska_sorter_projection.cpp index 978a6846..ecf30c89 100644 --- a/testsuite/sorters/ska_sorter_projection.cpp +++ b/testsuite/sorters/ska_sorter_projection.cpp @@ -11,6 +11,7 @@ #include #include #include +#include TEST_CASE( "ska_sorter tests with projections", "[ska_sorter][projection]" ) @@ -68,7 +69,7 @@ TEST_CASE( "ska_sorter tests with projections", SECTION( "sort with std::string iterators" ) { - struct wrapper { std::string value; }; + using wrapper = generic_wrapper; std::vector vec; for (int i = 0 ; i < 100'000 ; ++i) { diff --git a/testsuite/sorters/spread_sorter_projection.cpp b/testsuite/sorters/spread_sorter_projection.cpp index 4f3f9e26..29db4bdb 100644 --- a/testsuite/sorters/spread_sorter_projection.cpp +++ b/testsuite/sorters/spread_sorter_projection.cpp @@ -11,6 +11,7 @@ #include #include #include +#include TEST_CASE( "spread_sorter tests with projections", "[spread_sorter][projection]" ) @@ -68,7 +69,7 @@ TEST_CASE( "spread_sorter tests with projections", SECTION( "sort with std::string iterators" ) { - struct wrapper { std::string value; }; + using wrapper = generic_wrapper; std::vector vec; for (int i = 0 ; i < 100'000 ; ++i) { @@ -82,7 +83,7 @@ TEST_CASE( "spread_sorter tests with projections", SECTION( "reverse sort with std::string iterators" ) { - struct wrapper { std::string value; }; + using wrapper = generic_wrapper; std::vector vec; for (int i = 0 ; i < 100'000 ; ++i) { diff --git a/testsuite/sorters/std_sorter.cpp b/testsuite/sorters/std_sorter.cpp index 0c348cf0..5d68cafd 100644 --- a/testsuite/sorters/std_sorter.cpp +++ b/testsuite/sorters/std_sorter.cpp @@ -11,6 +11,7 @@ #include #include #include +#include TEST_CASE( "std_sorter tests", "[std_sorter]" ) { @@ -54,7 +55,7 @@ TEST_CASE( "std_sorter tests with projections", std::mt19937_64 engine(Catch::rngSeed()); // Wrapper to hide the integer - struct wrapper { int value; }; + using wrapper = generic_wrapper; // Collection to sort std::vector vec(80); diff --git a/testsuite/testing-tools/wrapper.h b/testsuite/testing-tools/wrapper.h new file mode 100644 index 00000000..17442702 --- /dev/null +++ b/testsuite/testing-tools/wrapper.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_TESTSUITE_WRAPPER_H_ +#define CPPSORT_TESTSUITE_WRAPPER_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include + +//////////////////////////////////////////////////////////// +// Wrapper around a simple type +// +// This wrapper with a public value is mostly used to test +// pointer to data members used a projections throughout +// the test suite + +template +struct generic_wrapper +{ + T value; +}; + +//////////////////////////////////////////////////////////// +// Wrapper with an "order" +// +// This wrapper has an additional integral field meant to +// attach more information to it than the value it wraps, +// information which can be used to try to detect issues in +// stable sorting algorithms + +template +struct generic_stable_wrapper +{ + T value; + int order; + + friend auto operator<(const generic_stable_wrapper& lhs, const generic_stable_wrapper& rhs) + -> bool + { + if (lhs.value < rhs.value) { + return true; + } + if (rhs.value < lhs.value) { + return false; + } + return lhs.order < rhs.order; + } +}; + + +#endif // CPPSORT_TESTSUITE_WRAPPER_H_ diff --git a/testsuite/utility/chainable_projections.cpp b/testsuite/utility/chainable_projections.cpp index 27aa81c5..18bd01c2 100644 --- a/testsuite/utility/chainable_projections.cpp +++ b/testsuite/utility/chainable_projections.cpp @@ -11,11 +11,12 @@ #include #include #include +#include + +using wrapper = generic_wrapper; namespace { - struct wrapper { int value; }; - struct proj1: cppsort::utility::projection_base { From 307ad96d31b96673255b74332a60a34cf223568f Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 3 Dec 2020 20:31:20 +0100 Subject: [PATCH 22/90] Remove occurrences of the word "functor" [ci skip] --- .../cpp-sort/detail/spreadsort/detail/string_sort.h | 4 ++-- include/cpp-sort/detail/spreadsort/integer_sort.h | 2 +- include/cpp-sort/detail/spreadsort/string_sort.h | 10 +++++----- testsuite/adapters/hybrid_adapter_partial_compare.cpp | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/include/cpp-sort/detail/spreadsort/detail/string_sort.h b/include/cpp-sort/detail/spreadsort/detail/string_sort.h index 65a10466..cf749515 100644 --- a/include/cpp-sort/detail/spreadsort/detail/string_sort.h +++ b/include/cpp-sort/detail/spreadsort/detail/string_sort.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2017 Morwenn + * Copyright (c) 2015-2020 Morwenn * SPDX-License-Identifier: MIT */ @@ -87,7 +87,7 @@ namespace spreadsort } } - //This comparison functor assumes strings are identical up to char_offset + //This comparator assumes strings are identical up to char_offset template struct offset_less_than { diff --git a/include/cpp-sort/detail/spreadsort/integer_sort.h b/include/cpp-sort/detail/spreadsort/integer_sort.h index 06d37ba6..0e974536 100644 --- a/include/cpp-sort/detail/spreadsort/integer_sort.h +++ b/include/cpp-sort/detail/spreadsort/integer_sort.h @@ -66,7 +66,7 @@ Some performance plots of runtime vs. n and log(range) are provided:\n \post The elements in the range [@c first, @c last) are sorted in ascending order. \throws std::exception Propagates exceptions if any of the element comparisons, the element swaps (or moves), - the right shift, subtraction of right-shifted elements, functors, or any operations on iterators throw. + the right shift, subtraction of right-shifted elements, projections, or any operations on iterators throw. \warning Throwing an exception may cause data loss. This will also throw if a small vector resize throws, in which case there will be no data loss. \warning Invalid arguments cause undefined behaviour. diff --git a/include/cpp-sort/detail/spreadsort/string_sort.h b/include/cpp-sort/detail/spreadsort/string_sort.h index a04eb06b..fad28ea0 100644 --- a/include/cpp-sort/detail/spreadsort/string_sort.h +++ b/include/cpp-sort/detail/spreadsort/string_sort.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2016 Morwenn + * Copyright (c) 2015-2020 Morwenn * SPDX-License-Identifier: MIT */ @@ -64,7 +64,7 @@ Some performance plots of runtime vs. n and log(range) are provided:\n \post The elements in the range [@c first, @c last) are sorted in ascending order. \throws std::exception Propagates exceptions if any of the element comparisons, the element swaps (or moves), - the right shift, subtraction of right-shifted elements, functors, + the right shift, subtraction of right-shifted elements, callables, or any operations on iterators throw. \warning Throwing an exception may cause data loss. This will also throw if a small vector resize throws, in which case there will be no data loss. @@ -111,12 +111,12 @@ Some performance plots of runtime vs. n and log(range) are provided:\n \tparam RandomAccessIter Random access iterator - \tparam Comp Functor type to use for comparison. + \tparam Comp callable type to use for comparison. \tparam Unsigned_char_type Unsigned character type used for string. \param[in] first Iterator pointer to first element. \param[in] last Iterator pointing to one beyond the end of data. - \param[in] comp A binary functor that returns whether the first element passed to it should go before the second in order. + \param[in] comp A binary callable that returns whether the first element passed to it should go before the second in order. \param[in] unused value with the same type as the result of the [] operator, defining the Unsigned_char_type. The actual value is unused. \pre [@c first, @c last) is a valid range. @@ -129,7 +129,7 @@ Some performance plots of runtime vs. n and log(range) are provided:\n \return @c void. \throws std::exception Propagates exceptions if any of the element comparisons, the element swaps (or moves), - the right shift, subtraction of right-shifted elements, functors, + the right shift, subtraction of right-shifted elements, callables, or any operations on iterators throw. \warning Throwing an exception may cause data loss. This will also throw if a small vector resize throws, in which case there will be no data loss. diff --git a/testsuite/adapters/hybrid_adapter_partial_compare.cpp b/testsuite/adapters/hybrid_adapter_partial_compare.cpp index 4e38d02c..e483043a 100644 --- a/testsuite/adapters/hybrid_adapter_partial_compare.cpp +++ b/testsuite/adapters/hybrid_adapter_partial_compare.cpp @@ -101,7 +101,7 @@ TEST_CASE( "hybrid_adapter over partial comparison sorter", CHECK( res2 == sorter_type::descending ); } - SECTION( "with another functor" ) + SECTION( "with another function object" ) { sorter_type res1 = sorter(vec, std::less_equal<>{}); CHECK( res1 == sorter_type::generic ); From 983e08191fbf53f6002d1d237264a0949dd891f7 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 3 Dec 2020 21:00:07 +0100 Subject: [PATCH 23/90] Update the documentation's changelog page Cleanup the links and add information: - Mention the 128-bit integers support for counting_sorter - Mention the conditional std::mem_fn extended support - Mention the condition improvements brought by assumptions [ci skip] --- docs/Changelog.md | 40 ++++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/docs/Changelog.md b/docs/Changelog.md index 1cbf9cd7..672604bc 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -1,4 +1,4 @@ -This page describes the features that change in **cpp-sort** depending on the C++ version with which it is compiled (C++14 or later) as well as the support for miscellaneous compiler extensions; for a full changelog between actual releases, you can check the dedicated [releases page](https://github.com/Morwenn/cpp-sort/releases). +This page describes the features that change in **cpp-sort** depending on the C++ version with which it is compiled (C++14 or later) as well as the support for miscellaneous compiler extensions; for a full changelog between actual releases, you can check the dedicated [releases page][cpp-sort-releases]. ## C++14 features @@ -9,10 +9,10 @@ While **cpp-sort** theoretically requires a fully C++14-compliant compiler, a fe ## C++17 features -When compiled with C++17, **cpp-sort** might gain a few additional features depending on the level of C++17 support provided by the compiler. The availability of most of the features depends on the presence of corresponding [feature-testing macros](https://wg21.link/SD6). The support for feature-testing macros being optional in C++17, it is possible that some of the features listed below aren't available even though the compiler is implements them. If it is the case and it is a problem for you, don't hesitate to open an issue so that we can explicitly support the given compiler. +When compiled with C++17, **cpp-sort** might gain a few additional features depending on the level of C++17 support provided by the compiler. The availability of most of the features depends on the presence of corresponding [feature-testing macros][feature-test-macros]. The support for feature-testing macros being optional in C++17, it is possible that some of the features listed below aren't available even though the compiler is implements them. If it is the case and it is a problem for you, don't hesitate to open an issue so that we can explicitly support the given compiler. **New features:** -* `string_spread_sort` now accepts [`std::string_view`](https://en.cppreference.com/w/cpp/string/basic_string_view) and sometimes `std::wstring_view`. +* `string_spread_sort` now accepts [`std::string_view`][std-string-view] and sometimes `std::wstring_view`. This feature is made available through the check `__cplusplus > 201402L && __has_include()`. @@ -40,7 +40,7 @@ When compiled with C++17, **cpp-sort** might gain a few additional features depe This feature is made available through the check `__cpp_lib_uncaught_exceptions`. -* New [`function_constant`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#miscellaneous-function-objects) utility to micro-optimize function pointers and class member pointers. +* New [`function_constant`][cpp-sort-function-objects] utility to micro-optimize function pointers and class member pointers. ```cpp insertion_sort(collection, function_constant<&foo::bar>{}); @@ -61,19 +61,39 @@ When compiled with C++17, **cpp-sort** might gain a few additional features depe ## C++20 features -When compiled with C++20, **cpp-sort** might gain a few additional features depending on the level of C++20 support provided by the compiler. The availability of those features depends on the presence of corresponding [feature-testing macros](https://wg21.link/SD6) when possible, even though some checks are more granular. Don't hesitate to open an issue if your compiler and standard library supports one of those features but it doesn't seem to work in **cpp-sort**. +When compiled with C++20, **cpp-sort** might gain a few additional features depending on the level of C++20 support provided by the compiler. The availability of those features depends on the presence of corresponding [feature-testing macros][feature-test-macros] when possible, even though some checks are more granular. Don't hesitate to open an issue if your compiler and standard library supports one of those features but it doesn't seem to work in **cpp-sort**. **New features:** -* When available, [`std::identity`](https://en.cppreference.com/w/cpp/utility/functional/identity) benefits from dedicated support wherever [`utility::identity`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#miscellaneous-function-objects) is supported, with equivalent semantics. +* When available, [`std::identity`][std-identity] benefits from dedicated support wherever [`utility::identity`][cpp-sort-function-objects] is supported, with equivalent semantics. -* When available, [`std::ranges::less`](https://en.cppreference.com/w/cpp/utility/functional/ranges/less) and [`std::ranges::greater`](https://en.cppreference.com/w/cpp/utility/functional/ranges/greater) benefit from dedicated support wherever [`std::less<>`](https://en.cppreference.com/w/cpp/utility/functional/less_void) and [`std::greater<>`](https://en.cppreference.com/w/cpp/utility/functional/greater_void) are supported, with equivalent semantics. +* When available, [`std::ranges::less`][std-ranges-less] and [`std::ranges::greater`][std-ranges-greater] benefit from dedicated support wherever [`std::less<>`][std-less-void] and [`std::greater<>`][std-greater-void] are supported, with equivalent semantics. ## Other features -**cpp-sort** tries to take advantage of more than just standard features when possible, and also to provide extended support for some compiler-specific extensions. Below is a list of the impact that non-standard features might have on the library: +**cpp-sort** tries to take advantage of more than just standard features when possible by using implementation-specific tweaks to improve the user experience. The following improvements might be available depending on the your standard implementation: -**Extension-specific support:** -* 128-bit integers support: `ska_sorter` has dedicated support for 128-bit integers (`unsigned __int128` or `__uint128_t` and its signed counterpart), no matter whether the standard library is also instrumented for those types. This support should be available as long as `__SIZEOF_INT128__` is defined by the compiler. +**Additional features:** +* 128-bit integers support: [`counting_sorter`][counting-sorter] and [`ska_sorter`][ska-sorter] have dedicated support for 128-bit integers (`unsigned __int128` or `__uint128_t` and its signed counterpart), no matter whether the standard library is also instrumented for those types. This support should be available as long as the macro `__SIZEOF_INT128__` is defined. **Performance improvements:** * Bit manipulation intrinsics: there are a few places where bit tricks are used to perform a few operations faster. Some of those operations are made faster with bitwise manipulation intrinsics when those are available. + +* Assumptions: some algorithms use assumptions in select places to make the compiler generate more efficient code. Whether such assumptions are available depend on the compiler. + +* When using libstdc++ or libc++, the return type of [`std::mem_fn`][std-mem-fn] is considered ["probably branchless"][branchless-traits] when it wraps a pointer to data member, which can improve the speed of [`pdq_sorter`][pdq-sorter] and everything that relies on it in some scenarios. + + + [branchless-traits]: https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#branchless-traits + [counting-sorter]: https://github.com/Morwenn/cpp-sort/wiki/Sorters#counting_sorter + [cpp-sort-function-objects]: https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#miscellaneous-function-objects + [cpp-sort-releases]: https://github.com/Morwenn/cpp-sort/releases + [feature-test-macros]: https://wg21.link/SD6 + [pdq-sorter]: https://github.com/Morwenn/cpp-sort/wiki/Sorters#pdq_sorter + [ska-sorter]: https://github.com/Morwenn/cpp-sort/wiki/Sorters#ska_sorter + [std-greater-void]: https://en.cppreference.com/w/cpp/utility/functional/greater_void + [std-identity]: https://en.cppreference.com/w/cpp/utility/functional/identity + [std-less-void]: https://en.cppreference.com/w/cpp/utility/functional/less_void + [std-mem-fn]: https://en.cppreference.com/w/cpp/utility/functional/mem_fn + [std-ranges-greater]: https://en.cppreference.com/w/cpp/utility/functional/ranges/greater + [std-ranges-less]: https://en.cppreference.com/w/cpp/utility/functional/ranges/less + [std-string-view]: https://en.cppreference.com/w/cpp/string/basic_string_view) From 8c4c9136ddc5a9e0ccc4daccdb3b9e7911250feb Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 6 Dec 2020 00:19:10 +0100 Subject: [PATCH 24/90] New feature: cppsort::stable_t The type alias stable_t is kind of a better equivalent to stable_adapter: it aliases either a sorter directly if it is always stable, otherwise stable_adapter::type if it exists, otherwise stable_adapter itself. The goal is to alias the "least nested" type that is always stable to decrease template instantiation, template depth, and somehow bonary size. It also improves the error messages is some cases. Bundled with this feature: stable_adapter specializations now expose a member type named 'type' to implement the mechanism described above. The documentation page about sorters adapters was slightly cleaned up and reworked as part of this change. --- docs/Sorter-adapters.md | 77 ++++++++++++++----- include/cpp-sort/adapters/stable_adapter.h | 55 ++++++++++++- .../detail/stable_adapter_hybrid_adapter.h | 9 ++- .../detail/stable_adapter_self_sort_adapter.h | 9 ++- include/cpp-sort/sorters/default_sorter.h | 4 +- include/cpp-sort/sorters/std_sorter.h | 2 + include/cpp-sort/stable_sort.h | 39 +++++----- .../adapters/stable_adapter_every_sorter.cpp | 6 +- 8 files changed, 146 insertions(+), 55 deletions(-) diff --git a/docs/Sorter-adapters.md b/docs/Sorter-adapters.md index a84b0402..59be5b4e 100644 --- a/docs/Sorter-adapters.md +++ b/docs/Sorter-adapters.md @@ -6,7 +6,7 @@ Sorter adapters are the main reason for using sorter function objects instead of In this documentation, we will call *adapted sorters* the sorters passed to the adapters and *resulting sorter* the sorter class that results from the adaption of a sorter by an adapter. If not specified, the stability and the iterator category of the *resulting sorter* is that of the *adapted sorter* provided there is a single *adapted sorter*. -In C++17, *sorter adapters* can be used in a function-like fashion thanks to `explicit` constructors (taking one or several sorters) by taking advantage of implicit [deduction guides](http://en.cppreference.com/w/cpp/language/class_template_argument_deduction). The following example illustrates how it simplifies their use: +In C++17, *sorter adapters* can be used in a function-like fashion thanks to `explicit` constructors (taking one or several sorters) by taking advantage of implicit [deduction guides][ctad]. The following example illustrates how it simplifies their use: ```cpp // C++14 @@ -22,7 +22,7 @@ Most of the library's *sorter adapters* can store the passed *sorters* in their * If the *sorter adapter* adapts a single *sorter*, then it has a member function called `get()` which returns a reference to the internal *sorter* whose reference and `const` qualifications match those of the *sorter adapter* instance. If the *sorter adapter* is empty and default-constructible, then a default-constructed instance of the type of the *original sorter* is returned instead. * If the *sorter adapter* is empty and default-constructible, then it can be converted to any function pointer whose signature matches that of its `operator()`. -It is worth noting that in the current state of things, sorters & adapters are expected to have a `const operator()`, and thus don't play nice with *mutable sorters*. There are plans to properly handle *mutable sorters* in the future: you can track [the corresponding issue](https://github.com/Morwenn/cpp-sort/issues/104). +It is worth noting that in the current state of things, sorters & adapters are expected to have a `const operator()`, and thus don't play nice with *mutable sorters*. There are plans to properly handle *mutable sorters* in the future: you can track [the corresponding issue][issue-104]. *Changed in version 1.5.0:* adapters can store the sorters they adapt, enabling the use of *stateful sorters*. The overall semantics of sorters and adapters have evolved accordingly. @@ -95,7 +95,7 @@ This adapter uses `cppsort::iterator_category` to check the iterator category of If `hybrid_adapter` is nested in another `hybrid_adapter`, those are flattened: for example `hybrid_adapter, D>` is flattened to `hybrid_adapter`. This unwrapping exists so that the iterator categories of the sorters in the inner `hybrid_adapter` are seen by the outer one, and not only the fused iterator category of the inner `hybrid_adapter`. -If `hybrid_adapter` is wrapped into [`stable_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#stable_adapter), it wraps every *adapted sorter* into `stable_adapter`, forwarding it to better get the specific behaviour f some sorters or adapters when wrapped into it. +If `hybrid_adapter` is wrapped into [`stable_adapter`][stable-adapter], it wraps every *adapted sorter* into `stable_adapter`, forwarding it to better get the specific behaviour f some sorters or adapters when wrapped into it. The *resulting sorter*'s `is_always_stable` is `std::true_type` if and only if every *adapted sorter*'s `is_always_stable` is `std::true_type`. `is_stable` is specialized so that it will return the stability of the called *adapted sorter* with the given parameters. The iterator category of the *resulting sorter* is the most permissive iterator category among the *adapted sorters*. @@ -107,7 +107,7 @@ The *resulting sorter*'s `is_always_stable` is `std::true_type` if and only if e #include ``` -This adapter implements an indirect sort: a sorting algorithm that actually sorts the iterators rather than the values themselves, then uses the sorted iterators to move the actual values to their final position in the original collection. The actual algorithm used is a [mountain sort](https://github.com/Morwenn/mountain-sort), whose goal is to sort a collection while performing a minimal number of *move operations* on the elements of the collection. This indirect adapter copies the iterators and sorts them with the given sorter before performing cycles in a way close to a [cycle sort](https://en.wikipedia.org/wiki/Cycle_sort) to actually move the elements. There are a few differences though: while the cycle sort always has a O(n²) complexity, the *resulting sorter* of `indirect_adapter` has the complexity of the *adapted sorter*. However, it stores n additional iterators as well as n additional booleans and performs up to (3/2)n move operations once the iterators have been sorted; these operations are not significant enough to change the complexity of the *adapted sorter*, but they do represent a rather big additional constant factor. +This adapter implements an indirect sort: a sorting algorithm that actually sorts the iterators rather than the values themselves, then uses the sorted iterators to move the actual values to their final position in the original collection. The actual algorithm used is a [mountain sort][mountain-sort], whose goal is to sort a collection while performing a minimal number of *move operations* on the elements of the collection. This indirect adapter copies the iterators and sorts them with the given sorter before performing cycles in a way close to a [cycle sort][cycle-sort] to actually move the elements. There are a few differences though: while the cycle sort always has a O(n²) complexity, the *resulting sorter* of `indirect_adapter` has the complexity of the *adapted sorter*. However, it stores n additional iterators as well as n additional booleans and performs up to (3/2)n move operations once the iterators have been sorted; these operations are not significant enough to change the complexity of the *adapted sorter*, but they do represent a rather big additional constant factor. Note that `indirect_adapter` provides a rather good exception guarantee: as long as the collection of iterators is being sorted, if an exception is thrown, the collection to sort will remain in its original state. However, it doesn't provide the *strong exception guarantee* since exceptions could still be thrown when the elements are moved to their sorted position. @@ -118,7 +118,7 @@ template class indirect_adapter; ``` -The *resulting sorter* accepts forward iterators, and the iterator category of the *adapted sorter* does not matter. Note that this algorithm performs even fewer move operations than [`low_moves_sorter`](https://github.com/Morwenn/cpp-sort/wiki/Fixed-size-sorters#low_moves_sorter), but at the cost of a higher constant factor that may not always be worth it for small collections. +The *resulting sorter* accepts forward iterators, and the iterator category of the *adapted sorter* does not matter. Note that this algorithm performs even fewer move operations than [`low_moves_sorter`][low-moves-sorter], but at the cost of a higher constant factor that may not always be worth it for small collections. *Changed in version 1.3.0:* `indirect_adapter` now returns the result of the *adapted sorter* in C++17 mode. @@ -151,7 +151,7 @@ The *resulting sorter* accepts forward iterators, and the iterator category of t #include ``` -This adapter implements a [Schwartzian transform](https://en.wikipedia.org/wiki/Schwartzian_transform) that helps to reduce the runtime cost when projections are expensive. A regular sorting algorithm generally projects elements on-the-fly during a comparison, that is, every time a sorting algorithm performs a comparison, it projects both operands before comparing the results, which is ok when the projection operation is cheap, but which might become a problem when the projection is more expensive. A sorter wrapped into `schwartz_adapter` will instead precompute the projection for every element in the collection to sort, then sort the original collection according to the projected elements. Compared to a raw sorter, it requires O(n) additional space to store the projected elements. +This adapter implements a [Schwartzian transform][schwartzian-transform] that helps to reduce the runtime cost when projections are expensive. A regular sorting algorithm generally projects elements on-the-fly during a comparison, that is, every time a sorting algorithm performs a comparison, it projects both operands before comparing the results, which is ok when the projection operation is cheap, but which might become a problem when the projection is more expensive. A sorter wrapped into `schwartz_adapter` will instead precompute the projection for every element in the collection to sort, then sort the original collection according to the projected elements. Compared to a raw sorter, it requires O(n) additional space to store the projected elements. `schwartz_adapter` returns the result of the *adapted sorter* if any. @@ -172,7 +172,7 @@ The mechanism used to synchronize the collection of projected objects with the o #include ``` -This adapter takes a sorter and, if the collection to sort has a suitable `sort` method, it is used to sort the collection. Otherwise, if the collection to sort has a suitable `stable_sort` method, it is used to sort the collection. Otherwise, the *adapted sorter* is used instead to sort the collection. If `self_sort_adapter` is wrapped into [`stable_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#stable_adapter), if the collection to sort has a suitable `stable_sort` method, it is used to sort the collection (the `sort` methods of `std::list` and `std::forward_list` are special-cased and called by this adapter too). Otherwise, the *adapted sorter* wrapped into `stable_adapter` is used instead to sort the collection. +This adapter takes a sorter and, if the collection to sort has a suitable `sort` method, it is used to sort the collection. Otherwise, if the collection to sort has a suitable `stable_sort` method, it is used to sort the collection. Otherwise, the *adapted sorter* is used instead to sort the collection. If `self_sort_adapter` is wrapped into [`stable_adapter`][stable-adapter], if the collection to sort has a suitable `stable_sort` method, it is used to sort the collection (the `sort` methods of `std::list` and `std::forward_list` are special-cased and called by this adapter too). Otherwise, the *adapted sorter* wrapped into `stable_adapter` is used instead to sort the collection. This sorter adapter allows to support out-of-the-box sorting for `std::list` and `std::forward_list` as well as other user-defined classes that implement a `sort` method with a compatible interface. @@ -183,7 +183,7 @@ template struct self_sort_adapter; ``` -Since it is impossible to guarantee the stability of the `sort` method of a given iterable, the *resulting sorter*'s `is_always_stable` is `std::false_type`. However, [`is_stable`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-traits#is_stable) will be `std::true_type` if a container's `stable_sort` is called or if a call to the *adapted sorter* is stable. A special case considers valid calls to `std::list::sort` and `std::forward_list::sort` to be stable. +Since it is impossible to guarantee the stability of the `sort` method of a given iterable, the *resulting sorter*'s `is_always_stable` is `std::false_type`. However, [`is_stable`][is-stable] will be `std::true_type` if a container's `stable_sort` is called or if a call to the *adapted sorter* is stable. A special case considers valid calls to `std::list::sort` and `std::forward_list::sort` to be stable. ### `small_array_adapter` @@ -191,7 +191,7 @@ Since it is impossible to guarantee the stability of the `sort` method of a give #include ``` -This adapter is not a regular sorter adapter, but a *fixed-size sorter adapter*. It wraps a [fixed-size sorter](https://github.com/Morwenn/cpp-sort/wiki/Fixed-size-sorters) and calls it whenever it is passed a fixed-size C array or an `std::array` with an appropriate size. +This adapter is not a regular sorter adapter, but a *fixed-size sorter adapter*. It wraps a [fixed-size sorter][fixed-size-sorter] and calls it whenever it is passed a fixed-size C array or an `std::array` with an appropriate size. ```cpp template< @@ -201,7 +201,7 @@ template< struct small_array_adapter; ``` -The `Indices` parameter may be either `void` or a specialization of the standard class template [`std::index_sequence`](http://en.cppreference.com/w/cpp/utility/integer_sequence). When it is `void`, `small_array_adapter` will try to call the underlying fixed-size sorter for C arrays or `std::array` instances of any size. If an `std::index_sequence` specialization is given instead, the adapter will try to call the underlying fixed-size sorter only if the size of the array to be sorted appears in the index sequence. If the template parameter `Indices` is omitted, this class will check whether the [`fixed_sorter_traits`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-traits#fixed_sorter_traits) specialization for the given fixed-size sorter contains a type named `domain` and use it as indices; if such a type does not exist, `void` will be used as `Indices`. +The `Indices` parameter may be either `void` or a specialization of the standard class template [`std::index_sequence`][std-index-sequence]. When it is `void`, `small_array_adapter` will try to call the underlying fixed-size sorter for C arrays or `std::array` instances of any size. If an `std::index_sequence` specialization is given instead, the adapter will try to call the underlying fixed-size sorter only if the size of the array to be sorted appears in the index sequence. If the template parameter `Indices` is omitted, this class will check whether the [`fixed_sorter_traits`][fixed-sorter-traits] specialization for the given fixed-size sorter contains a type named `domain` and use it as indices; if such a type does not exist, `void` will be used as `Indices`. The `operator()` overloads are SFINAEd out if a collection not handled by the fixed-size sorter is passed (*e.g.* wrong type or array too big). These soft errors allow `small_array_adapter` to play nice with `hybrid_adapter`. For example, if one wants to call `low_moves_sorter` when a sorter is given an array of size 0 to 8 and `pdq_sorter` otherwise, they could easily create an appropriate sorter the following way: @@ -219,13 +219,19 @@ using sorter = cppsort::hybrid_adapter< *Warning: this adapter only supports default-constructible stateless sorters.* -### `stable_adapter` +### `stable_adapter`, `make_stable` and `stable_t` ```cpp #include ``` -This adapter takes a sorter and alters its behavior (if needed) to produce a stable sorter. It does so by associating every element of the collection to sort to its starting position and, whenever two elements compare equivalent, the algorithm uses the starting position of the compared elements to make sure that their relative starting positions are preserved. Compared to a raw sorter, it requires O(n) additional space to store the starting positions. +This adapter takes a sorter and alters its behavior (if needed) to produce a stable sorter. It does so by associating every element of the collection to sort to its starting position and, whenever two elements compare equivalent, the algorithm compares the starting positions of the elements to ensure that their relative starting positions are preserved. Compared to a raw sorter, it requires O(n) additional space to store the starting positions. + +If the *adapted sorter* already implements a stable sorting algorithm when called with a specific set of parameters (if [`is_stable`][is-stable] is `std::true_type` for the parameters), then the *resulting sorter* will call the *adapted sorter* directly. + +`stable_adapter` and its specializations might expose a `type` member type which aliases the *adapter sorter* or some intermediate sorter which is always stable, or the *resulting sorter* otherwise. Its goal is to provide the least nested type that is known to always be stable in order to sometimes skip some template nesting. + +The *resulting sorter* is always stable. `stable_adapter` returns the result of the *adapted sorter* if any. @@ -234,16 +240,16 @@ template struct stable_adapter; ``` -However, if the *adapted sorter* already implements a stable sorting algorithm when called with a specific set of parameters (if [`is_stable`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-traits#is_stable) is `std::true_type` for the parameters), then the *resulting sorter* will call the *adapted sorter* directly. The *resulting sorter* is always stable. +One can provide a dedicated stable algorithm by explicitly specializing `stable_adapter` to bypass the automatic transformation and allow optimizations, which can be useful when a pair of stable and unstable sorting algorithms are closely related. For example, while [`std_sorter`][std-sorter] calls [`std::sort`][std-sort], the explicit specialization `stable_adapter` calls [`std::stable_sort`][std-stable-sort] instead. In **cpp-sort**, `stable_adapter` has specializations for the following components: -One can provide a dedicated stable algorithm by explicitly specializing `stable_adapter` to bypass the automatic transformation and allow optimizations (this isn't clean *per se*, but...), which can be useful when a pair of stable and unstable sorting algorithms are closely related. For example, while [`std_sorter`](https://github.com/Morwenn/cpp-sort/wiki/Sorters#std_sorter) calls [`std::sort`](http://en.cppreference.com/w/cpp/algorithm/sort), the explicit specialization `stable_adapter` actually calls [`std::stable_sort`](http://en.cppreference.com/w/cpp/algorithm/stable_sort) instead. In **cpp-sort**, `stable_adapter` has specializations for the following sorters and adapters: +* [`default_sorter`][default-sorter] +* [`std_sorter`][std-sorter] +* [`hybrid_adapter`][hybrid-adapter] +* [`self_sort_adapter`][self-sort-adapter] -* [`default_sorter`](https://github.com/Morwenn/cpp-sort/wiki/Sorters#default_sorter) -* [`std_sorter`](https://github.com/Morwenn/cpp-sort/wiki/Sorters#std_sorter) -* [`hybrid_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#hybrid_adapter) -* [`self_sort_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#self_sort_adapter) +If such a user specialization is provided, it shall alias `is_always_stable` to `std::true_type` and provide a `type` member type which follows the rules mentioned earlier. -While `stable_adapter` is the "high-level" adapter whenever one wants a stable sorting algorithm, the header also provides `make_stable`, which directly exposes the raw mechanism used to transform an unstable sorter into a stable one without applying any of the short-circuits described above: +While `stable_adapter` is the "high-level" adapter to use whenever one wants a stable sorting algorithm, the header also provides `make_stable`, which directly exposes the raw mechanism used to transform an unstable sorter into a stable one without applying any of the short-circuits described above: ```cpp template @@ -252,13 +258,24 @@ struct make_stable; Contrary to `stable_adapter`, `make_stable` isn't meant to be specialized by end users. In C++17, `make_stable` like most of the other adapters can take advantage of deduction guides. +The header also provides the `stable_t` type alias, which either alias the passed sorter if it is always stable, or `stable_adapter` otherwise. + +```cpp +template +using stable_t = /* implementation-defined */; +``` + +*New in version 1.9.0:* `stable_t` + +It is roughly equivalent to `stable_adapter::type`, except that it doesn't instantiate `stable_adapter` when it doesn't need to. It can be used to reduce the template nesting and improve error messages in some places, and as such is often a better alternative to a raw `stable_adapter`. + ### `verge_adapter` ```cpp #include ``` -While the library already provides a `verge_sorter` built on top of `pdq_sorter`, the true power of vergesort is to add a fast *Runs*-adaptive layer on top of any sorting algorithm to make it handle data with big runs better while not being noticeably slower for the distributions that the vergesort layer can't handle. [This page](https://github.com/Morwenn/vergesort/blob/master/fallbacks.md) contains benchmarks of vergesort on top of several sorting algorithms, showing that it can be valuable tool to add on top of most sorting algorithms. +While the library already provides a `verge_sorter` built on top of `pdq_sorter`, the true power of vergesort is to add a fast *Runs*-adaptive layer on top of any sorting algorithm to make it handle data with big runs better while not being noticeably slower for the distributions that the vergesort layer can't handle. [This page][vergesort-fallbacks] contains benchmarks of vergesort on top of several sorting algorithms, showing that it can be valuable tool to add on top of most sorting algorithms. `verge_adapter` takes any sorter and uses it as a fallback sorting algorithm when it can't sort a collection on its own. The *resulting sorter* is always unstable, no matter the stability of the *adapted sorter*. It only accepts random-access iterables. @@ -266,3 +283,23 @@ While the library already provides a `verge_sorter` built on top of `pdq_sorter` template struct verge_adapter; ``` + + + [ctad]: https://en.cppreference.com/w/cpp/language/class_template_argument_deduction + [cycle-sort]: https://en.wikipedia.org/wiki/Cycle_sort + [default-sorter]: https://github.com/Morwenn/cpp-sort/wiki/Sorters#default_sorter + [fixed-size-sorter]: https://github.com/Morwenn/cpp-sort/wiki/Fixed-size-sorters + [fixed-sorter-traits]: https://github.com/Morwenn/cpp-sort/wiki/Sorter-traits#fixed_sorter_traits + [hybrid-adapter]: https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#hybrid_adapter + [is-stable]: https://github.com/Morwenn/cpp-sort/wiki/Sorter-traits#is_stable + [issue-104]: https://github.com/Morwenn/cpp-sort/issues/104 + [low-moves-sorter]: https://github.com/Morwenn/cpp-sort/wiki/Fixed-size-sorters#low_moves_sorter + [mountain-sort]: https://github.com/Morwenn/mountain-sort + [schwartzian-transform]: https://en.wikipedia.org/wiki/Schwartzian_transform + [stable-adapter]: https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#stable_adapter + [self-sort-adapter]: https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#self_sort_adapter + [std-index-sequence]: https://en.cppreference.com/w/cpp/utility/integer_sequence + [std-sort]: https://en.cppreference.com/w/cpp/algorithm/sort + [std-sorter]: https://github.com/Morwenn/cpp-sort/wiki/Sorters#std_sorter + [std-stable-sort]: https://en.cppreference.com/w/cpp/algorithm/stable_sort + [vergesort-fallbacks]: https://github.com/Morwenn/vergesort/blob/master/fallbacks.md diff --git a/include/cpp-sort/adapters/stable_adapter.h b/include/cpp-sort/adapters/stable_adapter.h index 707e795a..9ff28dcb 100644 --- a/include/cpp-sort/adapters/stable_adapter.h +++ b/include/cpp-sort/adapters/stable_adapter.h @@ -133,7 +133,7 @@ namespace cppsort } //////////////////////////////////////////////////////////// - // Adapter + // make_stable_impl template struct make_stable_impl: @@ -187,6 +187,9 @@ namespace cppsort }; } + //////////////////////////////////////////////////////////// + // make_stable + // Expose the underlying mechanism template struct make_stable: @@ -199,7 +202,9 @@ namespace cppsort {} }; - // Actual sorter + //////////////////////////////////////////////////////////// + // stable_adapter + template struct stable_adapter: utility::adapter_storage, @@ -240,13 +245,57 @@ namespace cppsort // Sorter traits using is_always_stable = std::true_type; + using type = stable_adapter; }; // Accidental nesting can happen, unwrap template struct stable_adapter>: stable_adapter - {}; + { + using type = stable_adapter; + }; + + //////////////////////////////////////////////////////////// + // stable_t + + namespace detail + { + template + struct stable_t_impl_false + { + // The sorter is not always stable and does not have + // a type member named 'type' + using type = stable_adapter; + }; + + template + struct stable_t_impl_false::type>> + { + // The sorter is not always stable but has a type member + // called 'type', use that one + using type = typename stable_adapter::type; + }; + + template + struct stable_t_impl + { + // The sorter is always stable, alias it directly + using type = Sorter; + }; + + template + struct stable_t_impl + { + using type = typename stable_t_impl_false::type; + }; + } + + template + using stable_t = typename detail::stable_t_impl< + Sorter, + cppsort::is_always_stable_v + >::type; } #ifdef CPPSORT_ADAPTERS_HYBRID_ADAPTER_DONE_ diff --git a/include/cpp-sort/detail/stable_adapter_hybrid_adapter.h b/include/cpp-sort/detail/stable_adapter_hybrid_adapter.h index 9d878617..e938780c 100644 --- a/include/cpp-sort/detail/stable_adapter_hybrid_adapter.h +++ b/include/cpp-sort/detail/stable_adapter_hybrid_adapter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2019 Morwenn + * Copyright (c) 2018-2020 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_STABLE_ADAPTER_HYBRID_ADAPTER_H_ @@ -16,14 +16,14 @@ namespace cppsort { template struct stable_adapter>: - hybrid_adapter...> + hybrid_adapter...> { private: template constexpr explicit stable_adapter(std::index_sequence, hybrid_adapter&& sorters): - hybrid_adapter...>( - (stable_adapter(std::move(sorters).template get()))... + hybrid_adapter...>( + (stable_t(std::move(sorters).template get()))... ) {} @@ -42,6 +42,7 @@ namespace cppsort // Sorter traits using is_always_stable = std::true_type; + using type = hybrid_adapter...>; }; } diff --git a/include/cpp-sort/detail/stable_adapter_self_sort_adapter.h b/include/cpp-sort/detail/stable_adapter_self_sort_adapter.h index 92e2a468..a0ca5c46 100644 --- a/include/cpp-sort/detail/stable_adapter_self_sort_adapter.h +++ b/include/cpp-sort/detail/stable_adapter_self_sort_adapter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2019 Morwenn + * Copyright (c) 2016-2020 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_STABLE_ADAPTER_SELF_SORT_ADAPTER_H_ @@ -22,7 +22,7 @@ namespace cppsort { template struct stable_adapter>: - utility::adapter_storage>, + utility::adapter_storage>, detail::check_iterator_category, detail::sorter_facade_fptr< stable_adapter>, @@ -35,8 +35,8 @@ namespace cppsort stable_adapter() = default; constexpr explicit stable_adapter(self_sort_adapter sorter): - utility::adapter_storage>( - stable_adapter(std::move(sorter.get())) + utility::adapter_storage>( + stable_t(std::move(sorter.get())) ) {} @@ -114,6 +114,7 @@ namespace cppsort // Sorter traits using is_always_stable = std::true_type; + using type = stable_adapter>; }; } diff --git a/include/cpp-sort/sorters/default_sorter.h b/include/cpp-sort/sorters/default_sorter.h index 02463901..3f675928 100644 --- a/include/cpp-sort/sorters/default_sorter.h +++ b/include/cpp-sort/sorters/default_sorter.h @@ -30,7 +30,9 @@ namespace cppsort template<> struct stable_adapter: merge_sorter - {}; + { + using type = merge_sorter; + }; //////////////////////////////////////////////////////////// // Unstable sorter diff --git a/include/cpp-sort/sorters/std_sorter.h b/include/cpp-sort/sorters/std_sorter.h index 8a2df537..91c0cbe1 100644 --- a/include/cpp-sort/sorters/std_sorter.h +++ b/include/cpp-sort/sorters/std_sorter.h @@ -111,6 +111,8 @@ namespace cppsort constexpr explicit stable_adapter(std_sorter) noexcept: sorter_facade() {} + + using type = stable_adapter; }; //////////////////////////////////////////////////////////// diff --git a/include/cpp-sort/stable_sort.h b/include/cpp-sort/stable_sort.h index 0f445804..b3df6508 100644 --- a/include/cpp-sort/stable_sort.h +++ b/include/cpp-sort/stable_sort.h @@ -26,7 +26,7 @@ namespace cppsort auto stable_sort(Iterable&& iterable) -> void { - stable_adapter{}(std::forward(iterable)); + stable_t{}(std::forward(iterable)); } template< @@ -38,7 +38,7 @@ namespace cppsort auto stable_sort(Iterable&& iterable, Compare compare) -> void { - stable_adapter{}(std::forward(iterable), std::move(compare)); + stable_t{}(std::forward(iterable), std::move(compare)); } template< @@ -54,8 +54,8 @@ namespace cppsort auto stable_sort(Iterable&& iterable, Compare compare, Projection projection) -> void { - stable_adapter{}(std::forward(iterable), - std::move(compare), std::move(projection)); + stable_t{}(std::forward(iterable), + std::move(compare), std::move(projection)); } template @@ -63,7 +63,7 @@ namespace cppsort auto stable_sort(Iterator first, Iterator last) -> void { - stable_adapter{}(std::move(first), std::move(last)); + stable_t{}(std::move(first), std::move(last)); } template< @@ -75,8 +75,7 @@ namespace cppsort auto stable_sort(Iterator first, Iterator last, Compare compare) -> void { - stable_adapter{}(std::move(first), std::move(last), - std::move(compare)); + stable_t{}(std::move(first), std::move(last), std::move(compare)); } template< @@ -92,8 +91,8 @@ namespace cppsort auto stable_sort(Iterator first, Iterator last, Compare compare, Projection projection) -> void { - return stable_adapter{}(std::move(first), std::move(last), - std::move(compare), std::move(projection)); + return stable_t{}(std::move(first), std::move(last), + std::move(compare), std::move(projection)); } //////////////////////////////////////////////////////////// @@ -110,7 +109,7 @@ namespace cppsort auto stable_sort(const Sorter&, Iterable&& iterable) -> decltype(auto) { - return stable_adapter{}(std::forward(iterable)); + return stable_t{}(std::forward(iterable)); } template< @@ -126,7 +125,7 @@ namespace cppsort auto stable_sort(const Sorter&, Iterable&& iterable, Func func) -> decltype(auto) { - return stable_adapter{}(std::forward(iterable), std::move(func)); + return stable_t{}(std::forward(iterable), std::move(func)); } template< @@ -140,22 +139,22 @@ namespace cppsort Compare compare, Projection projection) -> decltype(auto) { - return stable_adapter{}(std::forward(iterable), - std::move(compare), std::move(projection)); + return stable_t{}(std::forward(iterable), + std::move(compare), std::move(projection)); } template< typename Sorter, typename Iterator, typename = std::enable_if_t, Iterator + stable_t, Iterator >> > CPPSORT_DEPRECATED("cppsort::stable_sort() is deprecated and will be removed in version 2.0.0") auto stable_sort(const Sorter&, Iterator first, Iterator last) -> decltype(auto) { - return stable_adapter{}(std::move(first), std::move(last)); + return stable_t{}(std::move(first), std::move(last)); } template< @@ -163,15 +162,15 @@ namespace cppsort typename Iterator, typename Func, typename = std::enable_if_t< - is_comparison_sorter_iterator_v, Iterator, Func> || - is_projection_sorter_iterator_v, Iterator, Func> + is_comparison_sorter_iterator_v, Iterator, Func> || + is_projection_sorter_iterator_v, Iterator, Func> > > CPPSORT_DEPRECATED("cppsort::stable_sort() is deprecated and will be removed in version 2.0.0") auto stable_sort(const Sorter&, Iterator first, Iterator last, Func func) -> decltype(auto) { - return stable_adapter{}(std::move(first), std::move(last), std::move(func)); + return stable_t{}(std::move(first), std::move(last), std::move(func)); } template< @@ -185,8 +184,8 @@ namespace cppsort Compare compare, Projection projection) -> decltype(auto) { - return stable_adapter{}(std::move(first), std::move(last), - std::move(compare), std::move(projection)); + return stable_t{}(std::move(first), std::move(last), + std::move(compare), std::move(projection)); } } diff --git a/testsuite/adapters/stable_adapter_every_sorter.cpp b/testsuite/adapters/stable_adapter_every_sorter.cpp index 63260917..2a3ab832 100644 --- a/testsuite/adapters/stable_adapter_every_sorter.cpp +++ b/testsuite/adapters/stable_adapter_every_sorter.cpp @@ -47,7 +47,7 @@ TEMPLATE_TEST_CASE( "every random-access sorter with stable_adapter", "[stable_a std::shuffle(collection.begin(), collection.end(), engine); helpers::iota(collection.begin(), collection.end(), 0, &wrapper::order); - cppsort::stable_adapter sorter; + cppsort::stable_t sorter; sorter(collection, &wrapper::value); CHECK( std::is_sorted(collection.begin(), collection.end()) ); } @@ -71,7 +71,7 @@ TEMPLATE_TEST_CASE( "every bidirectional sorter with stable_adapter", "[stable_a helpers::iota(collection.begin(), collection.end(), 0, &wrapper::order); std::list li(collection.begin(), collection.end()); - cppsort::stable_adapter sorter; + cppsort::stable_t sorter; sorter(li, &wrapper::value); CHECK( std::is_sorted(li.begin(), li.end()) ); } @@ -92,7 +92,7 @@ TEMPLATE_TEST_CASE( "every forward sorter with with stable_adapter", "[stable_ad helpers::iota(collection.begin(), collection.end(), 0, &wrapper::order); std::forward_list fli(collection.begin(), collection.end()); - cppsort::stable_adapter sorter; + cppsort::stable_t sorter; sorter(fli, &wrapper::value); CHECK( std::is_sorted(fli.begin(), fli.end()) ); } From ef81d411b8d2a42b2759bcde9e7fafa31da47a10 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 6 Dec 2020 20:43:47 +0100 Subject: [PATCH 25/90] Make test suite wrappers back_inserter-compatible This change allows to use them with distributions. This commit also takes advantage of this to change several parts of the test suite to use distribution, and applies some drive-by cleanups. --- testsuite/adapters/counting_adapter.cpp | 35 +++----- testsuite/adapters/mixed_adapters.cpp | 8 +- .../schwartz_adapter_every_sorter.cpp | 45 +++++----- ...schwartz_adapter_every_sorter_reversed.cpp | 66 ++++++++------- .../adapters/stable_adapter_every_sorter.cpp | 44 ++++------ testsuite/every_sorter_no_post_iterator.cpp | 48 +++++------ .../sorters/default_sorter_projection.cpp | 12 +-- .../merge_insertion_sorter_projection.cpp | 12 +-- testsuite/sorters/merge_sorter_projection.cpp | 12 +-- testsuite/sorters/ska_sorter.cpp | 57 ++++++------- testsuite/sorters/spread_sorter.cpp | 56 ++++++------- testsuite/sorters/std_sorter.cpp | 57 +++++-------- testsuite/testing-tools/wrapper.h | 60 ++++++++++++- testsuite/utility/as_projection.cpp | 84 +++++++++---------- testsuite/utility/as_projection_iterable.cpp | 53 ++++++------ testsuite/utility/chainable_projections.cpp | 26 +++--- 16 files changed, 334 insertions(+), 341 deletions(-) diff --git a/testsuite/adapters/counting_adapter.cpp b/testsuite/adapters/counting_adapter.cpp index ded8fabd..46a2753e 100644 --- a/testsuite/adapters/counting_adapter.cpp +++ b/testsuite/adapters/counting_adapter.cpp @@ -4,10 +4,9 @@ */ #include #include +#include #include #include -#include -#include #include #include #include @@ -35,29 +34,25 @@ TEST_CASE( "basic counting_adapter tests", // Fill the collection std::list collection; auto distribution = dist::shuffled{}; - distribution(std::back_inserter(collection), 65, 0); + distribution(std::back_inserter(collection), 65); // Sort and check it's sorted std::size_t res = sorter(collection); CHECK( res == 2080 ); - CHECK( std::is_sorted(std::begin(collection), std::end(collection)) ); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); } SECTION( "with projections" ) { - // Pseudo-random number engine - std::mt19937_64 engine(Catch::rngSeed()); - // Fill the collection - std::vector tmp(80); - helpers::iota(std::begin(tmp), std::end(tmp), 0, &wrapper::value); - std::shuffle(std::begin(tmp), std::end(tmp), engine); - std::list collection(std::begin(tmp), std::end(tmp)); + std::list collection; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(collection), 80); // Sort and check it's sorted std::size_t res = sorter(collection, &wrapper::value); CHECK( res == 3160 ); - CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), + CHECK( helpers::is_sorted(collection.begin(), collection.end(), std::less<>{}, &wrapper::value) ); } } @@ -78,7 +73,7 @@ TEST_CASE( "counting_adapter tests with std_sorter", // Sort and check it's sorted sorter(collection); - CHECK( std::is_sorted(std::begin(collection), std::end(collection)) ); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); } } @@ -99,24 +94,20 @@ TEST_CASE( "counting_adapter with span", // Sort and check it's sorted std::size_t res = sorter(make_span(collection)); CHECK( res == 2080 ); - CHECK( std::is_sorted(std::begin(collection), std::end(collection)) ); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); } SECTION( "with projections" ) { - // Pseudo-random number engine - std::mt19937_64 engine(Catch::rngSeed()); - // Fill the collection - std::vector tmp(80); - helpers::iota(std::begin(tmp), std::end(tmp), 0, &wrapper::value); - std::shuffle(std::begin(tmp), std::end(tmp), engine); - std::list collection(std::begin(tmp), std::end(tmp)); + std::list collection; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(collection), 80, 0); // Sort and check it's sorted std::size_t res = sorter(make_span(collection), &wrapper::value); CHECK( res == 3160 ); - CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), + CHECK( helpers::is_sorted(collection.begin(), collection.end(), std::less<>{}, &wrapper::value) ); } } diff --git a/testsuite/adapters/mixed_adapters.cpp b/testsuite/adapters/mixed_adapters.cpp index bd09fd59..4137b102 100644 --- a/testsuite/adapters/mixed_adapters.cpp +++ b/testsuite/adapters/mixed_adapters.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include @@ -98,10 +97,9 @@ TEST_CASE( "indirect sort with Schwartzian transform", { using wrapper = generic_wrapper; - std::vector collection(334); - helpers::iota(std::begin(collection), std::end(collection), -93, &wrapper::value); - std::mt19937 engine(Catch::rngSeed()); - std::shuffle(std::begin(collection), std::end(collection), engine); + std::vector collection; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(collection), 334, -93); SECTION( "schwartz_adapter over indirect_adapter" ) { diff --git a/testsuite/adapters/schwartz_adapter_every_sorter.cpp b/testsuite/adapters/schwartz_adapter_every_sorter.cpp index b28ad811..e7d65245 100644 --- a/testsuite/adapters/schwartz_adapter_every_sorter.cpp +++ b/testsuite/adapters/schwartz_adapter_every_sorter.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include // NOTE: this test used to use wrapper, but it was later @@ -42,10 +43,9 @@ TEMPLATE_TEST_CASE( "every random-access sorter with Schwartzian transform adapt cppsort::tim_sorter, cppsort::verge_sorter ) { - std::vector> collection(412); - helpers::iota(collection.begin(), collection.end(), -125, &wrapper<>::value); - std::mt19937 engine(Catch::rngSeed()); - std::shuffle(collection.begin(), collection.end(), engine); + std::vector> collection; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(collection), 412, -125); cppsort::schwartz_adapter sorter; sorter(collection, &wrapper<>::value); @@ -62,15 +62,14 @@ TEMPLATE_TEST_CASE( "every bidirectional sorter with Schwartzian transform adapt cppsort::selection_sorter, cppsort::verge_sorter ) { - std::vector> collection(412); - helpers::iota(collection.begin(), collection.end(), -125, &wrapper<>::value); - std::mt19937 engine(Catch::rngSeed()); - std::shuffle(collection.begin(), collection.end(), engine); + std::list> collection; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(collection), 412, -125); - std::list> li(collection.begin(), collection.end()); cppsort::schwartz_adapter sorter; - sorter(li, &wrapper<>::value); - CHECK( helpers::is_sorted(li.begin(), li.end(), std::less<>{}, &wrapper<>::value) ); + sorter(collection, &wrapper<>::value); + CHECK( helpers::is_sorted(collection.begin(), collection.end(), + std::less<>{}, &wrapper<>::value) ); } TEMPLATE_TEST_CASE( "every forward sorter with Schwartzian transform adapter", "[schwartz_adapter]", @@ -79,31 +78,31 @@ TEMPLATE_TEST_CASE( "every forward sorter with Schwartzian transform adapter", " cppsort::quick_sorter, cppsort::selection_sorter ) { - std::vector> collection(412); - helpers::iota(collection.begin(), collection.end(), -125, &wrapper<>::value); - std::mt19937 engine(Catch::rngSeed()); - std::shuffle(collection.begin(), collection.end(), engine); + std::forward_list> collection; + auto distribution = dist::shuffled{}; + distribution(std::front_inserter(collection), 412, -125); - std::forward_list> fli(collection.begin(), collection.end()); cppsort::schwartz_adapter sorter; - sorter(fli, &wrapper<>::value); - CHECK( helpers::is_sorted(fli.begin(), fli.end(), std::less<>{}, &wrapper<>::value) ); + sorter(collection, &wrapper<>::value); + CHECK( helpers::is_sorted(collection.begin(), collection.end(), + std::less<>{}, &wrapper<>::value) ); } TEST_CASE( "type-specific sorters with Schwartzian transform adapter", "[schwartz_adapter]" ) { + auto distribution = dist::shuffled{}; + std::vector> collection(412); - helpers::iota(collection.begin(), collection.end(), -125, &wrapper<>::value); + distribution(std::back_inserter(collection), 412, -125); + std::vector> collection2(412); - helpers::iota(collection2.begin(), collection2.end(), -125, &wrapper::value); + distribution(std::back_inserter(collection2), 412, -125); + std::vector> collection3; for (int i = -125 ; i < 287 ; ++i) { collection3.push_back({std::to_string(i)}); } - std::mt19937 engine(Catch::rngSeed()); - std::shuffle(collection.begin(), collection.end(), engine); - std::shuffle(collection2.begin(), collection2.end(), engine); std::shuffle(collection3.begin(), collection3.end(), engine); SECTION( "ska_sorter" ) diff --git a/testsuite/adapters/schwartz_adapter_every_sorter_reversed.cpp b/testsuite/adapters/schwartz_adapter_every_sorter_reversed.cpp index fd9e9b2a..d89617c8 100644 --- a/testsuite/adapters/schwartz_adapter_every_sorter_reversed.cpp +++ b/testsuite/adapters/schwartz_adapter_every_sorter_reversed.cpp @@ -3,15 +3,17 @@ * SPDX-License-Identifier: MIT */ #include -#include +#include #include #include #include +#include #include #include #include #include #include +#include #include // NOTE: this test used to use wrapper, but it was later @@ -41,46 +43,48 @@ TEMPLATE_TEST_CASE( "every sorter with Schwartzian transform adapter and reverse cppsort::tim_sorter, cppsort::verge_sorter ) { - std::vector> collection(412); - helpers::iota(std::begin(collection), std::end(collection), -125, &wrapper<>::value); - std::mt19937 engine(Catch::rngSeed()); - std::shuffle(std::begin(collection), std::end(collection), engine); + std::vector> collection; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(collection), 412, -125); cppsort::schwartz_adapter sorter; - sorter(std::rbegin(collection), std::rend(collection), &wrapper<>::value); - CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), + sorter(collection.rbegin(), collection.rend(), &wrapper<>::value); + CHECK( helpers::is_sorted(collection.begin(), collection.end(), std::greater<>{}, &wrapper<>::value) ); } TEST_CASE( "type-specific sorters with Schwartzian transform adapter and reverse iterators", "[schwartz_adapter][reverse_iterator]" ) { - std::vector> collection(412); - helpers::iota(std::begin(collection), std::end(collection), -125, &wrapper<>::value); - std::vector> collection2(412); - helpers::iota(std::begin(collection2), std::end(collection2), -125, &wrapper::value); - std::vector> collection3; - for (int i = -125 ; i < 287 ; ++i) { collection3.push_back({std::to_string(i)}); } + auto distribution = dist::shuffled{}; + + std::vector> collection; + distribution(std::back_inserter(collection), 412, -125); + std::vector> collection2; + distribution(std::back_inserter(collection2), 412, -125); + + std::vector> collection3; + for (int i = -125 ; i < 287 ; ++i) { + collection3.push_back({std::to_string(i)}); + } std::mt19937 engine(Catch::rngSeed()); - std::shuffle(std::begin(collection), std::end(collection), engine); - std::shuffle(std::begin(collection2), std::end(collection2), engine); - std::shuffle(std::begin(collection3), std::end(collection3), engine); + std::shuffle(collection3.begin(), collection3.end(), engine); SECTION( "ska_sorter" ) { cppsort::schwartz_adapter sorter; - sorter(std::rbegin(collection), std::rend(collection), &wrapper<>::value); - CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), + sorter(collection.rbegin(), collection.rend(), &wrapper<>::value); + CHECK( helpers::is_sorted(collection.begin(), collection.end(), std::greater<>{}, &wrapper<>::value) ); - sorter(std::rbegin(collection2), std::rend(collection2), &wrapper::value); - CHECK( helpers::is_sorted(std::begin(collection2), std::end(collection2), + sorter(collection2.rbegin(), collection2.rend(), &wrapper::value); + CHECK( helpers::is_sorted(collection2.begin(), collection2.end(), std::greater<>{}, &wrapper::value) ); - sorter(std::rbegin(collection3), std::rend(collection3), &wrapper::value); - CHECK( helpers::is_sorted(std::begin(collection3), std::end(collection3), + sorter(collection3.rbegin(), collection3.rend(), &wrapper::value); + CHECK( helpers::is_sorted(collection3.begin(), collection3.end(), std::greater<>{}, &wrapper::value) ); } @@ -88,22 +92,22 @@ TEST_CASE( "type-specific sorters with Schwartzian transform adapter and reverse { cppsort::schwartz_adapter sorter; - sorter(std::rbegin(collection), std::rend(collection), &wrapper<>::value); - CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), + sorter(collection.rbegin(), collection.rend(), &wrapper<>::value); + CHECK( helpers::is_sorted(collection.begin(), collection.end(), std::greater<>{}, &wrapper<>::value) ); - sorter(std::rbegin(collection2), std::rend(collection2), &wrapper::value); - CHECK( helpers::is_sorted(std::begin(collection2), std::end(collection2), + sorter(collection2.rbegin(), collection2.rend(), &wrapper::value); + CHECK( helpers::is_sorted(collection2.begin(), collection2.end(), std::greater<>{}, &wrapper::value) ); - sorter(std::rbegin(collection3), std::rend(collection3), &wrapper::value); - CHECK( helpers::is_sorted(std::begin(collection3), std::end(collection3), + sorter(collection3.rbegin(), collection3.rend(), &wrapper::value); + CHECK( helpers::is_sorted(collection3.begin(), collection3.end(), std::greater<>{}, &wrapper::value) ); - std::shuffle(std::begin(collection3), std::end(collection3), engine); - sorter(std::rbegin(collection3), std::rend(collection3), + std::shuffle(collection3.begin(), collection3.end(), engine); + sorter(collection3.rbegin(), collection3.rend(), std::greater<>{}, &wrapper::value); - CHECK( helpers::is_sorted(std::begin(collection3), std::end(collection3), + CHECK( helpers::is_sorted(collection3.begin(), collection3.end(), std::less<>{}, &wrapper::value) ); } } diff --git a/testsuite/adapters/stable_adapter_every_sorter.cpp b/testsuite/adapters/stable_adapter_every_sorter.cpp index 2a3ab832..db4551fc 100644 --- a/testsuite/adapters/stable_adapter_every_sorter.cpp +++ b/testsuite/adapters/stable_adapter_every_sorter.cpp @@ -3,16 +3,16 @@ * SPDX-License-Identifier: MIT */ #include -#include #include +#include #include -#include #include #include #include #include #include #include +#include #include using wrapper = generic_stable_wrapper; @@ -38,13 +38,9 @@ TEMPLATE_TEST_CASE( "every random-access sorter with stable_adapter", "[stable_a cppsort::tim_sorter, cppsort::verge_sorter ) { - std::vector collection(412); - std::size_t count = 0; - for (wrapper& wrap: collection) { - wrap.value = count++ % 17; - } - std::mt19937 engine(Catch::rngSeed()); - std::shuffle(collection.begin(), collection.end(), engine); + std::vector collection; + auto distribution = dist::shuffled_16_values{}; + distribution(std::back_inserter(collection), 412); helpers::iota(collection.begin(), collection.end(), 0, &wrapper::order); cppsort::stable_t sorter; @@ -61,19 +57,14 @@ TEMPLATE_TEST_CASE( "every bidirectional sorter with stable_adapter", "[stable_a cppsort::selection_sorter, cppsort::verge_sorter ) { - std::vector collection(412); - std::size_t count = 0; - for (wrapper& wrap: collection) { - wrap.value = count++ % 17; - } - std::mt19937 engine(Catch::rngSeed()); - std::shuffle(collection.begin(), collection.end(), engine); + std::list collection; + auto distribution = dist::shuffled_16_values{}; + distribution(std::back_inserter(collection), 412); helpers::iota(collection.begin(), collection.end(), 0, &wrapper::order); - std::list li(collection.begin(), collection.end()); cppsort::stable_t sorter; - sorter(li, &wrapper::value); - CHECK( std::is_sorted(li.begin(), li.end()) ); + sorter(collection, &wrapper::value); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); } TEMPLATE_TEST_CASE( "every forward sorter with with stable_adapter", "[stable_adapter]", @@ -82,17 +73,12 @@ TEMPLATE_TEST_CASE( "every forward sorter with with stable_adapter", "[stable_ad cppsort::quick_sorter, cppsort::selection_sorter ) { - std::vector collection(412); - std::size_t count = 0; - for (wrapper& wrap: collection) { - wrap.value = count++ % 17; - } - std::mt19937 engine(Catch::rngSeed()); - std::shuffle(collection.begin(), collection.end(), engine); + std::forward_list collection; + auto distribution = dist::shuffled_16_values{}; + distribution(std::front_inserter(collection), 412); helpers::iota(collection.begin(), collection.end(), 0, &wrapper::order); - std::forward_list fli(collection.begin(), collection.end()); cppsort::stable_t sorter; - sorter(fli, &wrapper::value); - CHECK( std::is_sorted(fli.begin(), fli.end()) ); + sorter(collection, &wrapper::value); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); } diff --git a/testsuite/every_sorter_no_post_iterator.cpp b/testsuite/every_sorter_no_post_iterator.cpp index 227f16f5..baf602e5 100644 --- a/testsuite/every_sorter_no_post_iterator.cpp +++ b/testsuite/every_sorter_no_post_iterator.cpp @@ -5,10 +5,12 @@ #include #include #include +#include #include #include #include #include +#include #include TEMPLATE_TEST_CASE( "test most sorters with no_post_iterator", "[sorters]", @@ -35,19 +37,16 @@ TEMPLATE_TEST_CASE( "test most sorters with no_post_iterator", "[sorters]", cppsort::verge_sorter ) { std::vector collection; - for (int i = 56 ; i < 366 ; ++i) { - collection.emplace_back(i); - } - std::mt19937 engine(Catch::rngSeed()); - std::shuffle(std::begin(collection), std::end(collection), engine); + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(collection), 310, -56); // Iterators with no post-increment and no post-decrement - auto first = make_no_post_iterator(std::begin(collection)); - auto last = make_no_post_iterator(std::end(collection)); + auto first = make_no_post_iterator(collection.begin()); + auto last = make_no_post_iterator(collection.end()); TestType sorter; sorter(first, last); - CHECK( std::is_sorted(std::begin(collection), std::end(collection)) ); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); } TEMPLATE_TEST_CASE( "test type-specific sorters with no_post_iterator further", @@ -56,44 +55,39 @@ TEMPLATE_TEST_CASE( "test type-specific sorters with no_post_iterator further", cppsort::spread_sorter ) { TestType sorter; + auto distribution = dist::shuffled{}; std::vector collection_float; - for (float i = 56.0f ; i < 366.0f ; ++i) { - collection_float.emplace_back(i); - } - std::mt19937 engine(Catch::rngSeed()); - std::shuffle(std::begin(collection_float), std::end(collection_float), engine); + distribution(std::back_inserter(collection_float), 310, -56); // Iterators with no post-increment and no post-decrement - auto first_float = make_no_post_iterator(std::begin(collection_float)); - auto last_float = make_no_post_iterator(std::end(collection_float)); + auto first_float = make_no_post_iterator(collection_float.begin()); + auto last_float = make_no_post_iterator(collection_float.end()); sorter(first_float, last_float); - CHECK( std::is_sorted(std::begin(collection_float), std::end(collection_float)) ); + CHECK( std::is_sorted(collection_float.begin(), collection_float.end()) ); std::vector collection_double; - for (double i = 56.0 ; i < 366.0 ; ++i) { - collection_double.emplace_back(i); - } - std::shuffle(std::begin(collection_double), std::end(collection_double), engine); + distribution(std::back_inserter(collection_double), 310, -56); // Iterators with no post-increment and no post-decrement - auto first_double = make_no_post_iterator(std::begin(collection_double)); - auto last_double = make_no_post_iterator(std::end(collection_double)); + auto first_double = make_no_post_iterator(collection_double.begin()); + auto last_double = make_no_post_iterator(collection_double.end()); sorter(first_double, last_double); - CHECK( std::is_sorted(std::begin(collection_double), std::end(collection_double)) ); + CHECK( std::is_sorted(collection_double.begin(), collection_double.end()) ); std::vector collection_str; for (long i = 56 ; i < 366 ; ++i) { collection_str.emplace_back(std::to_string(i)); } - std::shuffle(std::begin(collection_str), std::end(collection_str), engine); + std::mt19937 engine(Catch::rngSeed()); + std::shuffle(collection_str.begin(), collection_str.end(), engine); // Iterators with no post-increment and no post-decrement - auto first_str = make_no_post_iterator(std::begin(collection_str)); - auto last_str = make_no_post_iterator(std::end(collection_str)); + auto first_str = make_no_post_iterator(collection_str.begin()); + auto last_str = make_no_post_iterator(collection_str.end()); sorter(first_str, last_str); - CHECK( std::is_sorted(std::begin(collection_str), std::end(collection_str)) ); + CHECK( std::is_sorted(collection_str.begin(), collection_str.end()) ); } diff --git a/testsuite/sorters/default_sorter_projection.cpp b/testsuite/sorters/default_sorter_projection.cpp index 2bbc0629..1c61846a 100644 --- a/testsuite/sorters/default_sorter_projection.cpp +++ b/testsuite/sorters/default_sorter_projection.cpp @@ -2,32 +2,28 @@ * Copyright (c) 2015-2020 Morwenn * SPDX-License-Identifier: MIT */ -#include #include #include #include #include -#include #include #include #include #include #include +#include #include TEST_CASE( "default sorter tests with projections", "[default_sorter][projection]" ) { - // Pseudo-random number engine - std::mt19937_64 engine(Catch::rngSeed()); - // Wrapper to hide the integer using wrapper = generic_wrapper; // Collection to sort - std::vector vec(80); - helpers::iota(std::begin(vec), std::end(vec), 0, &wrapper::value); - std::shuffle(std::begin(vec), std::end(vec), engine); + std::vector vec; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(vec), 80); SECTION( "sort with random-access iterable" ) { diff --git a/testsuite/sorters/merge_insertion_sorter_projection.cpp b/testsuite/sorters/merge_insertion_sorter_projection.cpp index 76dea3d3..1d983ccf 100644 --- a/testsuite/sorters/merge_insertion_sorter_projection.cpp +++ b/testsuite/sorters/merge_insertion_sorter_projection.cpp @@ -2,29 +2,25 @@ * Copyright (c) 2016-2020 Morwenn * SPDX-License-Identifier: MIT */ -#include #include #include -#include #include #include #include #include +#include #include TEST_CASE( "merge_insertion_sorter tests with projections", "[merge_insertion_sorter][projection]" ) { - // Pseudo-random number engine - std::mt19937_64 engine(Catch::rngSeed()); - // Wrapper to hide the integer using wrapper = generic_wrapper; // Collection to sort - std::vector vec(80); - helpers::iota(std::begin(vec), std::end(vec), 0, &wrapper::value); - std::shuffle(std::begin(vec), std::end(vec), engine); + std::vector vec; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(vec), 80); SECTION( "sort with random-access iterable" ) { diff --git a/testsuite/sorters/merge_sorter_projection.cpp b/testsuite/sorters/merge_sorter_projection.cpp index 0331cb8f..9b914871 100644 --- a/testsuite/sorters/merge_sorter_projection.cpp +++ b/testsuite/sorters/merge_sorter_projection.cpp @@ -2,31 +2,27 @@ * Copyright (c) 2015-2020 Morwenn * SPDX-License-Identifier: MIT */ -#include #include #include #include #include -#include #include #include #include #include +#include #include TEST_CASE( "merge_sorter tests with projections", "[merge_sorter][projection]" ) { - // Pseudo-random number engine - std::mt19937_64 engine(Catch::rngSeed()); - // Wrapper to hide the integer using wrapper = generic_wrapper; // Collection to sort - std::vector vec(80); - helpers::iota(std::begin(vec), std::end(vec), 0, &wrapper::value); - std::shuffle(std::begin(vec), std::end(vec), engine); + std::vector vec; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(vec), 80); SECTION( "sort with random-access iterable" ) { diff --git a/testsuite/sorters/ska_sorter.cpp b/testsuite/sorters/ska_sorter.cpp index 4f5a8ab4..3baf9630 100644 --- a/testsuite/sorters/ska_sorter.cpp +++ b/testsuite/sorters/ska_sorter.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include #include #include @@ -15,57 +14,52 @@ #include #include #include +#include TEST_CASE( "ska_sorter tests", "[ska_sorter]" ) { - // Pseudo-random number engine - std::mt19937_64 engine(Catch::rngSeed()); + auto distribution = dist::shuffled{}; SECTION( "sort with int iterable" ) { - std::vector vec(100'000); - std::iota(std::begin(vec), std::end(vec), 0); - std::shuffle(std::begin(vec), std::end(vec), engine); + std::vector vec; + distribution(std::back_inserter(vec), 100'000); cppsort::ska_sort(vec); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); } #ifdef __SIZEOF_INT128__ SECTION( "sort with unsigned int128 iterable" ) { - std::vector<__uint128_t> vec(100'000); - std::iota(std::begin(vec), std::end(vec), __uint128_t(0)); - std::shuffle(std::begin(vec), std::end(vec), engine); + std::vector<__uint128_t> vec; + distribution(std::back_inserter(vec), 100'000); cppsort::ska_sort(vec); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); } #endif SECTION( "sort with unsigned int iterators" ) { - std::vector vec(100'000); - std::iota(std::begin(vec), std::end(vec), 0u); - std::shuffle(std::begin(vec), std::end(vec), engine); - cppsort::ska_sort(std::begin(vec), std::end(vec)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + std::vector vec; + distribution(std::back_inserter(vec), 100'000); + cppsort::ska_sort(vec.begin(), vec.end()); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); } SECTION( "sort with float iterable" ) { - std::vector vec(100'000); - std::iota(std::begin(vec), std::end(vec), 0.0f); - std::shuffle(std::begin(vec), std::end(vec), engine); + std::vector vec; + distribution(std::back_inserter(vec), 100'000); cppsort::ska_sort(vec); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); } SECTION( "sort with double iterators" ) { - std::vector vec(100'000); - std::iota(std::begin(vec), std::end(vec), 0.0); - std::shuffle(std::begin(vec), std::end(vec), engine); - cppsort::ska_sort(std::begin(vec), std::end(vec)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + std::vector vec; + distribution(std::back_inserter(vec), 100'000); + cppsort::ska_sort(vec.begin(), vec.end()); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); } SECTION( "sort with std::string" ) @@ -75,13 +69,16 @@ TEST_CASE( "ska_sorter tests", "[ska_sorter]" ) vec.push_back(std::to_string(i)); } - std::shuffle(std::begin(vec), std::end(vec), engine); + // Pseudo-random number engine + std::mt19937_64 engine(Catch::rngSeed()); + + std::shuffle(vec.begin(), vec.end(), engine); cppsort::ska_sort(vec); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); - std::shuffle(std::begin(vec), std::end(vec), engine); - cppsort::ska_sort(std::begin(vec), std::end(vec)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + std::shuffle(vec.begin(), vec.end(), engine); + cppsort::ska_sort(vec.begin(), vec.end()); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); } } diff --git a/testsuite/sorters/spread_sorter.cpp b/testsuite/sorters/spread_sorter.cpp index f5348a6d..130ca5a7 100644 --- a/testsuite/sorters/spread_sorter.cpp +++ b/testsuite/sorters/spread_sorter.cpp @@ -4,52 +4,50 @@ */ #include #include -#include #include #include #include #include #include +#include TEST_CASE( "spread_sorter tests", "[spread_sorter]" ) { // Pseudo-random number engine std::mt19937_64 engine(Catch::rngSeed()); + auto distribution = dist::shuffled{}; + SECTION( "sort with int iterable" ) { - std::vector vec(100'000); - std::iota(std::begin(vec), std::end(vec), 0); - std::shuffle(std::begin(vec), std::end(vec), engine); + std::vector vec; + distribution(std::back_inserter(vec), 100'000); cppsort::spread_sort(vec); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); } SECTION( "sort with unsigned int iterators" ) { - std::vector vec(100'000); - std::iota(std::begin(vec), std::end(vec), 0u); - std::shuffle(std::begin(vec), std::end(vec), engine); - cppsort::spread_sort(std::begin(vec), std::end(vec)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + std::vector vec; + distribution(std::back_inserter(vec), 100'000); + cppsort::spread_sort(vec.begin(), vec.end()); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); } SECTION( "sort with float iterable" ) { - std::vector vec(100'000); - std::iota(std::begin(vec), std::end(vec), 0.0f); - std::shuffle(std::begin(vec), std::end(vec), engine); + std::vector vec; + distribution(std::back_inserter(vec), 100'000); cppsort::spread_sort(vec); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); } SECTION( "sort with double iterators" ) { - std::vector vec(100'000); - std::iota(std::begin(vec), std::end(vec), 0.0); - std::shuffle(std::begin(vec), std::end(vec), engine); - cppsort::spread_sort(std::begin(vec), std::end(vec)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + std::vector vec; + distribution(std::back_inserter(vec), 100'000); + cppsort::spread_sort(vec.begin(), vec.end()); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); } SECTION( "sort with std::string" ) @@ -59,13 +57,13 @@ TEST_CASE( "spread_sorter tests", "[spread_sorter]" ) vec.push_back(std::to_string(i)); } - std::shuffle(std::begin(vec), std::end(vec), engine); + std::shuffle(vec.begin(), vec.end(), engine); cppsort::spread_sort(vec); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); - std::shuffle(std::begin(vec), std::end(vec), engine); - cppsort::spread_sort(std::begin(vec), std::end(vec)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + std::shuffle(vec.begin(), vec.end(), engine); + cppsort::spread_sort(vec.begin(), vec.end()); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); } SECTION( "reverse sort with std::string" ) @@ -75,12 +73,12 @@ TEST_CASE( "spread_sorter tests", "[spread_sorter]" ) vec.push_back(std::to_string(i)); } - std::shuffle(std::begin(vec), std::end(vec), engine); + std::shuffle(vec.begin(), vec.end(), engine); cppsort::spread_sort(vec, std::greater<>{}); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); - std::shuffle(std::begin(vec), std::end(vec), engine); - cppsort::spread_sort(std::begin(vec), std::end(vec), std::greater<>{}); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + std::shuffle(vec.begin(), vec.end(), engine); + cppsort::spread_sort(vec.begin(), vec.end(), std::greater<>{}); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); } } diff --git a/testsuite/sorters/std_sorter.cpp b/testsuite/sorters/std_sorter.cpp index 5d68cafd..5062a519 100644 --- a/testsuite/sorters/std_sorter.cpp +++ b/testsuite/sorters/std_sorter.cpp @@ -5,98 +5,85 @@ #include #include #include -#include -#include #include #include #include #include +#include #include TEST_CASE( "std_sorter tests", "[std_sorter]" ) { - // Pseudo-random number engine - std::mt19937_64 engine(Catch::rngSeed()); - - // Collection to sort - std::vector vec(80); - std::iota(std::begin(vec), std::end(vec), 0); - std::shuffle(std::begin(vec), std::end(vec), engine); + std::vector vec; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(vec), 80); SECTION( "sort with iterable" ) { cppsort::std_sort(vec); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); } SECTION( "sort with iterable and compare" ) { cppsort::std_sort(vec, std::greater<>{}); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); } SECTION( "sort with iterators" ) { - cppsort::std_sort(std::begin(vec), std::end(vec)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + cppsort::std_sort(vec.begin(), vec.end()); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); } SECTION( "sort with iterators and compare" ) { - cppsort::std_sort(std::begin(vec), std::end(vec), std::greater<>{}); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + cppsort::std_sort(vec.begin(), vec.end(), std::greater<>{}); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); } } TEST_CASE( "std_sorter tests with projections", "[std_sorter][projection]" ) { - // Pseudo-random number engine - std::mt19937_64 engine(Catch::rngSeed()); - // Wrapper to hide the integer using wrapper = generic_wrapper; - // Collection to sort - std::vector vec(80); - helpers::iota(std::begin(vec), std::end(vec), 0, &wrapper::value); - std::shuffle(std::begin(vec), std::end(vec), engine); + std::vector vec; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(vec), 80); SECTION( "sort with iterable" ) { cppsort::std_sort(vec, &wrapper::value); - CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::less<>{}, &wrapper::value) ); + CHECK( helpers::is_sorted(vec.begin(), vec.end(), std::less<>{}, &wrapper::value) ); } SECTION( "sort with iterable and compare" ) { cppsort::std_sort(vec, std::greater<>{}, &wrapper::value); - CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}, &wrapper::value) ); + CHECK( helpers::is_sorted(vec.begin(), vec.end(), std::greater<>{}, &wrapper::value) ); } SECTION( "sort with iterators" ) { - cppsort::std_sort(std::begin(vec), std::end(vec), &wrapper::value); - CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::less<>{}, &wrapper::value) ); + cppsort::std_sort(vec.begin(), vec.end(), &wrapper::value); + CHECK( helpers::is_sorted(vec.begin(), vec.end(), std::less<>{}, &wrapper::value) ); } SECTION( "sort with iterators and compare" ) { - cppsort::std_sort(std::begin(vec), std::end(vec), std::greater<>{}, &wrapper::value); - CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}, &wrapper::value) ); + cppsort::std_sort(vec.begin(), vec.end(), std::greater<>{}, &wrapper::value); + CHECK( helpers::is_sorted(vec.begin(), vec.end(), std::greater<>{}, &wrapper::value) ); } } TEST_CASE( "stable_adapter tests", "[std_sorter][stable_adapter]" ) { - // Pseudo-random number engine - std::mt19937_64 engine(Catch::rngSeed()); - - // Collection to sort - std::vector vec(80); - std::iota(vec.begin(), vec.end(), 0); - std::shuffle(vec.begin(), vec.end(), engine); + std::vector vec; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(vec), 80); auto sort = cppsort::stable_adapter(cppsort::std_sort); sort(vec); diff --git a/testsuite/testing-tools/wrapper.h b/testsuite/testing-tools/wrapper.h index 17442702..a3efab1c 100644 --- a/testsuite/testing-tools/wrapper.h +++ b/testsuite/testing-tools/wrapper.h @@ -8,7 +8,7 @@ //////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// -#include +#include //////////////////////////////////////////////////////////// // Wrapper around a simple type @@ -21,6 +21,35 @@ template struct generic_wrapper { T value; + + generic_wrapper() = default; + generic_wrapper(const generic_wrapper&) = default; + generic_wrapper(generic_wrapper&&) = default; + + constexpr generic_wrapper(const T& other_value): + value(other_value) + {} + + constexpr generic_wrapper(T&& other_value): + value(std::move(other_value)) + {} + + generic_wrapper& operator=(const generic_wrapper&) = default; + generic_wrapper& operator=(generic_wrapper&&) = default; + + constexpr auto operator=(const T& other_value) + -> generic_wrapper& + { + value = other_value; + return *this; + } + + constexpr auto operator=(T&& other_value) + -> generic_wrapper& + { + value = std::move(other_value); + return *this; + } }; //////////////////////////////////////////////////////////// @@ -37,6 +66,35 @@ struct generic_stable_wrapper T value; int order; + generic_stable_wrapper() = default; + generic_stable_wrapper(const generic_stable_wrapper&) = default; + generic_stable_wrapper(generic_stable_wrapper&&) = default; + + constexpr generic_stable_wrapper(const T& other_value): + value(other_value) + {} + + constexpr generic_stable_wrapper(T&& other_value): + value(std::move(other_value)) + {} + + generic_stable_wrapper& operator=(const generic_stable_wrapper&) = default; + generic_stable_wrapper& operator=(generic_stable_wrapper&&) = default; + + constexpr auto operator=(const T& other_value) + -> generic_stable_wrapper& + { + value = other_value; + return *this; + } + + constexpr auto operator=(T&& other_value) + -> generic_stable_wrapper& + { + value = std::move(other_value); + return *this; + } + friend auto operator<(const generic_stable_wrapper& lhs, const generic_stable_wrapper& rhs) -> bool { diff --git a/testsuite/utility/as_projection.cpp b/testsuite/utility/as_projection.cpp index 21127d7f..1e7413c0 100644 --- a/testsuite/utility/as_projection.cpp +++ b/testsuite/utility/as_projection.cpp @@ -5,8 +5,6 @@ #include #include #include -#include -#include #include #include #include @@ -14,6 +12,7 @@ #include #include #include +#include namespace { @@ -40,10 +39,9 @@ namespace TEST_CASE( "try mixed comparison/projection function object", "[utility][as_projection]" ) { - std::vector collection(100); - std::iota(std::begin(collection), std::end(collection), 0); - std::mt19937 engine(Catch::rngSeed()); - std::shuffle(std::begin(collection), std::end(collection), engine); + std::vector collection; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(collection), 100); tricky_function func; cppsort::default_sorter sorter; @@ -53,108 +51,108 @@ TEST_CASE( "try mixed comparison/projection function object", { auto vec = collection; sorter(vec, func); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; - sorter(std::begin(vec), std::end(vec), func); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + sorter(vec.begin(), vec.end(), func); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; cppsort::pdq_sort(vec, func); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; cppsort::pdq_sort(vec, func); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; - cppsort::pdq_sort(std::begin(vec), std::end(vec), func); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + cppsort::pdq_sort(vec.begin(), vec.end(), func); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; stable_sorter(vec, func); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; - stable_sorter(std::begin(vec), std::end(vec), func); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + stable_sorter(vec.begin(), vec.end(), func); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; cppsort::stable_adapter{}(vec, func); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; - cppsort::stable_adapter{}(std::begin(vec), std::end(vec), func); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + cppsort::stable_adapter{}(vec.begin(), vec.end(), func); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); } SECTION( "with a function wrapped in as_projection" ) { auto vec = collection; sorter(vec, cppsort::utility::as_projection(func)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); vec = collection; - sorter(std::begin(vec), std::end(vec), cppsort::utility::as_projection(func)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + sorter(vec.begin(), vec.end(), cppsort::utility::as_projection(func)); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); vec = collection; cppsort::pdq_sort(vec, cppsort::utility::as_projection(func)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); vec = collection; - cppsort::pdq_sort(std::begin(vec), std::end(vec), cppsort::utility::as_projection(func)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + cppsort::pdq_sort(vec.begin(), vec.end(), cppsort::utility::as_projection(func)); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); vec = collection; stable_sorter(vec, cppsort::utility::as_projection(func)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); vec = collection; - stable_sorter(std::begin(vec), std::end(vec), cppsort::utility::as_projection(func)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + stable_sorter(vec.begin(), vec.end(), cppsort::utility::as_projection(func)); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); vec = collection; cppsort::stable_adapter{}(vec, cppsort::utility::as_projection(func)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); vec = collection; - cppsort::stable_adapter{}(std::begin(vec), std::end(vec), cppsort::utility::as_projection(func)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + cppsort::stable_adapter{}(vec.begin(), vec.end(), cppsort::utility::as_projection(func)); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); } SECTION( "with a function wrapped in as_comparison" ) { auto vec = collection; sorter(vec, cppsort::utility::as_comparison(func)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; - sorter(std::begin(vec), std::end(vec), cppsort::utility::as_comparison(func)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + sorter(vec.begin(), vec.end(), cppsort::utility::as_comparison(func)); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; cppsort::pdq_sort(vec, cppsort::utility::as_comparison(func)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; - cppsort::pdq_sort(std::begin(vec), std::end(vec), cppsort::utility::as_comparison(func)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + cppsort::pdq_sort(vec.begin(), vec.end(), cppsort::utility::as_comparison(func)); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; stable_sorter(vec, cppsort::utility::as_comparison(func)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; - stable_sorter(std::begin(vec), std::end(vec), cppsort::utility::as_comparison(func)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + stable_sorter(vec.begin(), vec.end(), cppsort::utility::as_comparison(func)); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; cppsort::stable_adapter{}(vec, cppsort::utility::as_comparison(func)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; - cppsort::stable_adapter{}(std::begin(vec), std::end(vec), cppsort::utility::as_comparison(func)); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + cppsort::stable_adapter{}(vec.begin(), vec.end(), cppsort::utility::as_comparison(func)); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); } } diff --git a/testsuite/utility/as_projection_iterable.cpp b/testsuite/utility/as_projection_iterable.cpp index e4e7b38e..dccf6bae 100644 --- a/testsuite/utility/as_projection_iterable.cpp +++ b/testsuite/utility/as_projection_iterable.cpp @@ -5,8 +5,6 @@ #include #include #include -#include -#include #include #include #include @@ -15,6 +13,7 @@ #include #include #include +#include namespace { @@ -123,11 +122,9 @@ TEST_CASE( "sorter_facade with sorters overloaded for iterables and mixed compar // as_projection, as_comparison and some additional sorter_facade // overloads - // Collection to sort - std::vector collection(100); - std::iota(std::begin(collection), std::end(collection), 0); - std::mt19937 engine(Catch::rngSeed()); - std::shuffle(std::begin(collection), std::end(collection), engine); + std::vector collection; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(collection), 100); auto vec = collection; tricky_function func; @@ -138,77 +135,77 @@ TEST_CASE( "sorter_facade with sorters overloaded for iterables and mixed compar { auto res1 = comp_sort(vec, func); CHECK( res1 == call::iterable ); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; - auto res2 = comp_sort(std::begin(vec), std::end(vec), func); + auto res2 = comp_sort(vec.begin(), vec.end(), func); CHECK( res2 == call::iterator ); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; auto res3 = comp_sort(vec, cppsort::utility::as_comparison(func)); CHECK( res3 == call::iterable ); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; - auto res4 = comp_sort(std::begin(vec), std::end(vec), cppsort::utility::as_comparison(func)); + auto res4 = comp_sort(vec.begin(), vec.end(), cppsort::utility::as_comparison(func)); CHECK( res4 == call::iterator ); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; auto res5 = comp_sort(vec, cppsort::utility::as_projection(func)); CHECK( res5 == call::iterable ); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); vec = collection; - auto res6 = comp_sort(std::begin(vec), std::end(vec), cppsort::utility::as_projection(func)); + auto res6 = comp_sort(vec.begin(), vec.end(), cppsort::utility::as_projection(func)); CHECK( res6 == call::iterator ); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); vec = collection; auto res7 = comp_sort(vec, func, cppsort::utility::as_projection(func)); CHECK( res7 == call::iterable ); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; - auto res8 = comp_sort(std::begin(vec), std::end(vec), func, + auto res8 = comp_sort(vec.begin(), vec.end(), func, cppsort::utility::as_projection(func)); CHECK( res8 == call::iterator ); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; auto res9 = comp_sort(vec, cppsort::utility::as_comparison(func), cppsort::utility::as_projection(func)); CHECK( res9 == call::iterable ); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); vec = collection; - auto res10 = comp_sort(std::begin(vec), std::end(vec), + auto res10 = comp_sort(vec.begin(), vec.end(), cppsort::utility::as_comparison(func), cppsort::utility::as_projection(func)); CHECK( res10 == call::iterator ); - CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}) ); + CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); } SECTION( "projection_sorter" ) { auto res1 = proj_sort(vec, cppsort::utility::as_projection(func)); CHECK( res1 == call::iterable ); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); vec = collection; - auto res2 = proj_sort(std::begin(vec), std::end(vec), cppsort::utility::as_projection(func)); + auto res2 = proj_sort(vec.begin(), vec.end(), cppsort::utility::as_projection(func)); CHECK( res2 == call::iterator ); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); vec = collection; auto res3 = proj_sort(vec, func); CHECK( res3 == call::iterable ); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); vec = collection; - auto res4 = proj_sort(std::begin(vec), std::end(vec), func); + auto res4 = proj_sort(vec.begin(), vec.end(), func); CHECK( res4 == call::iterator ); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); } } diff --git a/testsuite/utility/chainable_projections.cpp b/testsuite/utility/chainable_projections.cpp index 18bd01c2..70b64b66 100644 --- a/testsuite/utility/chainable_projections.cpp +++ b/testsuite/utility/chainable_projections.cpp @@ -39,10 +39,9 @@ namespace TEST_CASE( "Pipe a projection_base and function pointer", "[utility][projection_base]" ) { - std::vector vec(80); - helpers::iota(std::begin(vec), std::end(vec), 0, &wrapper::value); - std::mt19937_64 engine(Catch::rngSeed()); - std::shuffle(std::begin(vec), std::end(vec), engine); + std::vector vec; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(vec), 80); proj1 projection1; proj2 projection2; @@ -50,25 +49,25 @@ TEST_CASE( "Pipe a projection_base and function pointer", SECTION( "const projection" ) { cppsort::spin_sort(vec, &wrapper::value | projection1); - CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}, &wrapper::value) ); + CHECK( helpers::is_sorted(vec.begin(), vec.end(), std::greater<>{}, &wrapper::value) ); } SECTION( "chained const projection" ) { cppsort::spin_sort(vec, &wrapper::value | projection1 | std::negate<>{}); - CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::less<>{}, &wrapper::value) ); + CHECK( helpers::is_sorted(vec.begin(), vec.end(), std::less<>{}, &wrapper::value) ); } SECTION( "non-const projection" ) { cppsort::spin_sort(vec, &wrapper::value | projection2); - CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}, &wrapper::value) ); + CHECK( helpers::is_sorted(vec.begin(), vec.end(), std::greater<>{}, &wrapper::value) ); } SECTION( "chained non-const projection" ) { cppsort::spin_sort(vec, &wrapper::value | projection2 | std::negate<>{}); - CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::less<>{}, &wrapper::value) ); + CHECK( helpers::is_sorted(vec.begin(), vec.end(), std::less<>{}, &wrapper::value) ); } } @@ -85,7 +84,7 @@ TEST_CASE( "Pipe a projection_base several times", proj2 projection2; cppsort::spin_sort(vec, projection1 | projection2); - CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); cppsort::spin_sort(vec2, projection2 | projection1 | projection2); CHECK( std::is_sorted(std::begin(vec2), std::end(vec2), std::greater<>{}) ); @@ -94,16 +93,15 @@ TEST_CASE( "Pipe a projection_base several times", TEST_CASE( "Pipe a projection with as_projection", "[utility][projection_base]" ) { - std::vector vec(80); - helpers::iota(std::begin(vec), std::end(vec), 0, &wrapper::value); - std::mt19937_64 engine(Catch::rngSeed()); - std::shuffle(std::begin(vec), std::end(vec), engine); + std::vector vec; + auto distribution = dist::shuffled{}; + distribution(std::back_inserter(vec), 80); auto projection1 = cppsort::utility::as_projection(&wrapper::value); auto projection2 = proj1(); // Basic check cppsort::spin_sort(vec, projection1 | projection2); - CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}, &wrapper::value) ); + CHECK( helpers::is_sorted(vec.begin(), vec.end(), std::greater<>{}, &wrapper::value) ); } From 682692d2319ce2460cc044a99244bd880da290b5 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Fri, 11 Dec 2020 20:03:36 +0100 Subject: [PATCH 26/90] Test stable_adapter against a new descending_plateau distribution --- .../adapters/stable_adapter_every_sorter.cpp | 81 ++++++++++++++----- testsuite/testing-tools/distributions.h | 25 +++++- 2 files changed, 86 insertions(+), 20 deletions(-) diff --git a/testsuite/adapters/stable_adapter_every_sorter.cpp b/testsuite/adapters/stable_adapter_every_sorter.cpp index db4551fc..a1757675 100644 --- a/testsuite/adapters/stable_adapter_every_sorter.cpp +++ b/testsuite/adapters/stable_adapter_every_sorter.cpp @@ -4,7 +4,6 @@ */ #include #include -#include #include #include #include @@ -17,6 +16,16 @@ using wrapper = generic_stable_wrapper; +// Those tests do not prove that the algorithms sort stably, +// but are meant to sometimes identify cases where they don't +// +// In order to achieve that, each element is associated to +// its original position in the collection to sort, then the +// collection is sorted accorded to its "value" and we check +// that the collection is indeed sorted, but also that +// the original position of elements with equivalent values +// are also in ascending order + TEMPLATE_TEST_CASE( "every random-access sorter with stable_adapter", "[stable_adapter]", cppsort::block_sorter>, cppsort::default_sorter, @@ -38,14 +47,25 @@ TEMPLATE_TEST_CASE( "every random-access sorter with stable_adapter", "[stable_a cppsort::tim_sorter, cppsort::verge_sorter ) { - std::vector collection; - auto distribution = dist::shuffled_16_values{}; - distribution(std::back_inserter(collection), 412); + cppsort::stable_t sorter; + std::vector collection(412); helpers::iota(collection.begin(), collection.end(), 0, &wrapper::order); - cppsort::stable_t sorter; - sorter(collection, &wrapper::value); - CHECK( std::is_sorted(collection.begin(), collection.end()) ); + SECTION( "shuffled_16_values" ) + { + auto distribution = dist::shuffled_16_values{}; + distribution(collection.begin(), collection.size()); + sorter(collection, &wrapper::value); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); + } + + SECTION( "descending_plateau" ) + { + auto distribution = dist::descending_plateau{}; + distribution(collection.begin(), collection.size()); + sorter(collection, &wrapper::value); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); + } } TEMPLATE_TEST_CASE( "every bidirectional sorter with stable_adapter", "[stable_adapter]", @@ -57,14 +77,25 @@ TEMPLATE_TEST_CASE( "every bidirectional sorter with stable_adapter", "[stable_a cppsort::selection_sorter, cppsort::verge_sorter ) { - std::list collection; - auto distribution = dist::shuffled_16_values{}; - distribution(std::back_inserter(collection), 412); + cppsort::stable_t sorter; + std::list collection(412); helpers::iota(collection.begin(), collection.end(), 0, &wrapper::order); - cppsort::stable_t sorter; - sorter(collection, &wrapper::value); - CHECK( std::is_sorted(collection.begin(), collection.end()) ); + SECTION( "shuffled_16_values" ) + { + auto distribution = dist::shuffled_16_values{}; + distribution(collection.begin(), collection.size()); + sorter(collection, &wrapper::value); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); + } + + SECTION( "descending_plateau" ) + { + auto distribution = dist::descending_plateau{}; + distribution(collection.begin(), collection.size()); + sorter(collection, &wrapper::value); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); + } } TEMPLATE_TEST_CASE( "every forward sorter with with stable_adapter", "[stable_adapter]", @@ -73,12 +104,24 @@ TEMPLATE_TEST_CASE( "every forward sorter with with stable_adapter", "[stable_ad cppsort::quick_sorter, cppsort::selection_sorter ) { - std::forward_list collection; - auto distribution = dist::shuffled_16_values{}; - distribution(std::front_inserter(collection), 412); + cppsort::stable_t sorter; + const int size = 412; + std::forward_list collection(size); helpers::iota(collection.begin(), collection.end(), 0, &wrapper::order); - cppsort::stable_t sorter; - sorter(collection, &wrapper::value); - CHECK( std::is_sorted(collection.begin(), collection.end()) ); + SECTION( "shuffled_16_values" ) + { + auto distribution = dist::shuffled_16_values{}; + distribution(collection.begin(), size); + sorter(collection, &wrapper::value); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); + } + + SECTION( "descending_plateau" ) + { + auto distribution = dist::descending_plateau{}; + distribution(collection.begin(), size); + sorter(collection, &wrapper::value); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); + } } diff --git a/testsuite/testing-tools/distributions.h b/testsuite/testing-tools/distributions.h index 715cf83e..c9c35e3e 100644 --- a/testsuite/testing-tools/distributions.h +++ b/testsuite/testing-tools/distributions.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2019 Morwenn + * Copyright (c) 2015-2020 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_TESTSUITE_DISTRIBUTIONS_H_ @@ -218,6 +218,29 @@ namespace dist } } }; + + struct descending_plateau: + distribution + { + template + auto operator()(OutputIterator out, std::size_t size) const + -> void + { + std::size_t i = size; + while (i > 2 * size / 3) { + *out++ = i; + --i; + } + while (i > size / 3) { + *out++ = size / 2; + --i; + } + while (i > 0) { + *out++ = i; + --i; + } + } + }; } #endif // CPPSORT_TESTSUITE_DISTRIBUTIONS_H_ From b40c626235ae44ae379272fcaa14986ef36dece0 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 16 Dec 2020 00:27:32 +0100 Subject: [PATCH 27/90] Fix link to swappable concept in doc introduction Link to cppreference to make the doc more amenable. Also use the lowercase concept name, and remove the duplicate "concept" word. [ci skip] --- docs/Home.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Home.md b/docs/Home.md index 184d9282..c3939dd0 100644 --- a/docs/Home.md +++ b/docs/Home.md @@ -22,7 +22,7 @@ If the library throws any other exception, it will likely come from user code. T **cpp-sort** strives to follow the standard library guidance when it comes to self-move and self-swap operations: when sorting a collection of `T`, the expectations are as follows: * The algorithms in the library should never perform a blind self-move operation since there is no guarantee about the result of such an operation. -* However the different algorithms expect `T` to satisfy the C++20 [`Swappable` concept][swappable] concept. This concepts requires that, for any value `x` of type `T`, the following code doesn't change the value of `x`: +* However the different algorithms expect `T` to satisfy the C++20 [`swappable` concept][swappable]. This concepts requires that, for any value `x` of type `T`, the following code doesn't change the value of `x`: ```cpp using std::swap; swap(x, x); @@ -75,4 +75,4 @@ If you ever feel that this wiki is incomplete, that it needs more examples or mo Hope you have fun! - [swappable]: http://eel.is/c++draft/concept.swappable \ No newline at end of file + [swappable]: https://en.cppreference.com/w/cpp/concepts/swappable From 92593b86989298ffe274114a23f0c82196b8392a Mon Sep 17 00:00:00 2001 From: Morwenn Date: Fri, 18 Dec 2020 17:28:02 +0100 Subject: [PATCH 28/90] Change HTTP links to HTTPS when possible in the docs [ci skip] --- docs/Benchmarks.md | 2 +- docs/Comparators-and-projections.md | 15 ++++++++----- docs/Comparators.md | 18 ++++++++-------- docs/Library-nomenclature.md | 6 +++--- docs/Miscellaneous-utilities.md | 20 ++++++++--------- docs/Original-research.md | 2 +- docs/Sorter-facade.md | 6 +++--- docs/Sorter-traits.md | 8 +++---- docs/Sorters.md | 6 +++--- docs/Sorting-functions.md | 4 ++-- docs/Writing-a-bubble_sorter.md | 4 ++-- docs/Writing-a-container-aware-algorithm.md | 2 +- docs/Writing-a-sorter.md | 24 ++++++++++----------- 13 files changed, 61 insertions(+), 56 deletions(-) diff --git a/docs/Benchmarks.md b/docs/Benchmarks.md index d441b8ec..fef47f10 100644 --- a/docs/Benchmarks.md +++ b/docs/Benchmarks.md @@ -71,7 +71,7 @@ The analysis is pretty simple here: # Bidirectional iterables -Sorting algorithms that handle non-random-access iterators are often second class citizens, but **cpp-sort** still provides a few ones. The most interesting part is that we can see how generic sorting algorithms perform compared to algorithms such as [`std::list::sort`](http://en.cppreference.com/w/cpp/container/list/sort) which are aware of the data structure they are sorting. +Sorting algorithms that handle non-random-access iterators are often second class citizens, but **cpp-sort** still provides a few ones. The most interesting part is that we can see how generic sorting algorithms perform compared to algorithms such as [`std::list::sort`](https://en.cppreference.com/w/cpp/container/list/sort) which are aware of the data structure they are sorting. ![Benchmark speed of sorts with increasing size for std::list](https://i.imgur.com/Z2BDhpz.png) diff --git a/docs/Comparators-and-projections.md b/docs/Comparators-and-projections.md index 25919bca..18d3b76f 100644 --- a/docs/Comparators-and-projections.md +++ b/docs/Comparators-and-projections.md @@ -1,14 +1,14 @@ Most sorting algorithms in **cpp-sort** accept comparison and/or projection parameters. The library therefore considers these kinds of functions to be first-class citizens too and provides dedicated comparators, projections and tools to combine them and to solve common related problems. -All the functions and classes in **cpp-sort** that take comparison or projection functions as parameters expect [*Callable*][callable] parameters, which correspond to anything that can be used as the first parameter of [`std::invoke`](http://en.cppreference.com/w/cpp/utility/functional/invoke). This allows to pass entities such as pointers to members or pointer to member functions to the sorting algorithms; it should work out-of-the-box without any wrapping needed on the user side. +All the functions and classes in **cpp-sort** that take comparison or projection functions as parameters expect [*Callable*][callable] parameters, which correspond to anything that can be used as the first parameter of [`std::invoke`][std-invoke]. This allows to pass entities such as pointers to members or pointer to member functions to the sorting algorithms; it should work out-of-the-box without any wrapping needed on the user side. ### Related utilities Several of the [miscellaneous utilities][utilities] provided by the library are meant to interact with comparators and projections: -- [`as_comparison` and `as_projection`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#as_comparison-and-as_projection) are used to make it explicit whether an ambiguous function object should be used for comparison or for projection. -- [`as_function`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#as_function) can be used to turn any [*Callable*][callable] into an object invokable with regular parentheses. -- [`is_probably_branchless_comparison` and `is_probably_branchless_projection`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#branchless-traits) are type traits that can be used to mark whether functions are likely to be branchless when called with a specific type. -- [`identity`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#miscellaneous-function-objects) is the default projection returning the argument it is passed without modifying it. +- [`as_comparison` and `as_projection`][as-comparison-as-projection] are used to make it explicit whether an ambiguous function object should be used for comparison or for projection. +- [`as_function`][as-function] can be used to turn any [*Callable*][callable] into an object invokable with regular parentheses. +- [`is_probably_branchless_comparison` and `is_probably_branchless_projection`][branchless-traits] are type traits that can be used to mark whether functions are likely to be branchless when called with a specific type. +- [`identity`][misc-function-objects] is the default projection returning the argument it is passed without modifying it. ### LWG3031 @@ -19,6 +19,11 @@ This additional guarantee is allowed by the resolution of [LWG3031][lwg3031]. Ho *New in version 1.7.0* + [as-comparison-as-projection]: https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#as_comparison-and-as_projection + [as-function]: https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#as_function + [branchless-traits]: https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#branchless-traits [callable]: https://en.cppreference.com/w/cpp/named_req/Callable [lwg3031]: https://wg21.link/LWG3031 + [misc-function-objects]: https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#miscellaneous-function-objects + [std-invoke]: https://en.cppreference.com/w/cpp/utility/functional/invoke [utilities]: https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities diff --git a/docs/Comparators.md b/docs/Comparators.md index 215c73a8..47b4c4c1 100644 --- a/docs/Comparators.md +++ b/docs/Comparators.md @@ -1,4 +1,4 @@ -While comparators are not inherent to sorting *per se*, most sorting algorithms (at least in this library) are *comparison sorts*, hence providing some common useful comparators along with the sorters can be useful. Every comparator in this module satisfies the [`BinaryPredicate`](http://en.cppreference.com/w/cpp/concept/BinaryPredicate) library concept. +While comparators are not inherent to sorting *per se*, most sorting algorithms (at least in this library) are *comparison sorts*, hence providing some common useful comparators along with the sorters can be useful. Every comparator in this module satisfies the [`BinaryPredicate`](https://en.cppreference.com/w/cpp/concept/BinaryPredicate) library concept. Every non-refined comparator described below is also a [transparent comparator][transparent-comparator]. While this ability is not used by the library itself, it means that the comparators can be used with the standard library associative containers to compare heterogeneous objects without having to create temporaries. @@ -25,7 +25,7 @@ The comparators `total_less` and `total_order` are [customization points][custom That said, the comparators are currently unable to discriminate between quiet and signaling NaNs, so they compare equivalent. When it doesn't handle a type natively and ADL doesn't find any suitable `total_less` function in a class namespace, `cppsort::total_less` does *not* fall back to `operator<`; see [P0100][P0100] for the rationale (it applies to the whole `total_*` family of customization points). -Total order comparators are considered as [generating branchless code](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#branchless-traits) when comparing instances of a type that satisfies [`std::is_integral`](http://en.cppreference.com/w/cpp/types/is_integral). +Total order comparators are considered as [generating branchless code](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#branchless-traits) when comparing instances of a type that satisfies [`std::is_integral`](https://en.cppreference.com/w/cpp/types/is_integral). *Changed in version 1.5.0:* `total_greater` and `total_less` are respectively of type `total_greater_t` and `total_less_t`. @@ -47,7 +47,7 @@ The comparators `weak_less` and `weak_order` are [customization points][custom-p When it doesn't handle a type natively and ADL doesn't find any suitable `weak_less` function in a class namespace, `cppsort::weak_less` falls back to `cppsort::total_less` since a total order is also a weak order (it applies to the whole `weak_*` family of customization points). -Weak order comparators are considered as [generating branchless code](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#branchless-traits) when comparing instances of a type that satisfies [`std::is_integral`](http://en.cppreference.com/w/cpp/types/is_integral). +Weak order comparators are considered as [generating branchless code](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#branchless-traits) when comparing instances of a type that satisfies [`std::is_integral`](https://en.cppreference.com/w/cpp/types/is_integral). *Changed in version 1.5.0:* `weak_greater` and `weak_less` are respectively of type `weak_greater_t` and `weak_less_t`. @@ -62,7 +62,7 @@ The comparators `partial_less` and `partial_order` are [customization points][cu When it doesn't handle a type natively and ADL doesn't find any suitable `partial_less` function in a class namespace, `cppsort::partial_less` falls back to `cppsort::weak_less` since a weak order is also a partial order (it applies to the whole `partial_*` family of customization points). -Partial order comparators are considered as [generating branchless code](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#branchless-traits) when comparing instances of a type that satisfies [`std::is_arithmetic`](http://en.cppreference.com/w/cpp/types/is_arithmetic). +Partial order comparators are considered as [generating branchless code](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#branchless-traits) when comparing instances of a type that satisfies [`std::is_arithmetic`](https://en.cppreference.com/w/cpp/types/is_arithmetic). *Changed in version 1.5.0:* `partial_greater` and `partial_less` are respectively of type `partial_greater_t` and `partial_less_t`. @@ -113,14 +113,14 @@ The two-parameter version of the customization point calls the three-parameter o [case-sensitivity]: https://en.wikipedia.org/wiki/Case_sensitivity [cppcon2015-compare]: https://github.com/CppCon/CppCon2015/tree/master/Presentations/Comparison%20is%20not%20simple%2C%20but%20it%20can%20be%20simpler%20-%20Lawrence%20Crowl%20-%20CppCon%202015 - [custom-point]: http://ericniebler.com/2014/10/21/customization-point-design-in-c11-and-beyond/ - [is-digit]: http://en.cppreference.com/w/cpp/string/byte/isdigit + [custom-point]: https://ericniebler.com/2014/10/21/customization-point-design-in-c11-and-beyond/ + [is-digit]: https://en.cppreference.com/w/cpp/string/byte/isdigit [natural-sort]: https://en.wikipedia.org/wiki/Natural_sort_order [P0100]: http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/p0100r1.html [partial-order]: https://en.wikipedia.org/wiki/Partially_ordered_set#Formal_definition [refining]: https://github.com/Morwenn/cpp-sort/wiki/Refined-functions - [std-locale]: http://en.cppreference.com/w/cpp/locale/locale - [to-lower]: http://en.cppreference.com/w/cpp/locale/ctype/tolower + [std-locale]: https://en.cppreference.com/w/cpp/locale/locale + [to-lower]: https://en.cppreference.com/w/cpp/locale/ctype/tolower [total-order]: https://en.wikipedia.org/wiki/Total_order [transparent-comparator]: https://stackoverflow.com/q/20317413/1364752 - [weak-order]: https://en.wikipedia.org/wiki/Weak_ordering \ No newline at end of file + [weak-order]: https://en.wikipedia.org/wiki/Weak_ordering diff --git a/docs/Library-nomenclature.md b/docs/Library-nomenclature.md index c162e82d..f81f53ee 100644 --- a/docs/Library-nomenclature.md +++ b/docs/Library-nomenclature.md @@ -6,7 +6,7 @@ cppsort::utility::fixed_buffer<512> >; -* *Comparison function*: most of the sorting algorithms in the library are comparison sorts. It means that the algorithm uses a comparison function to know the order of the elements and sort them accordingly; such a comparison function shall take two values and have a return type convertible to `bool`. The available sorting algorithms transform comparison functions on the fly so that some pointers to member functions can also be used as comparison functions, as if called with [`std::invoke`](http://en.cppreference.com/w/cpp/utility/functional/invoke). The default comparison function used by the sorting algorithms is [`std::less<>`](http://en.cppreference.com/w/cpp/utility/functional/less_void). Many sorters can take a comparison function as an additional parameter. For example, using `std::greater<>` instead of the default comparison function would sort a collection in descending order. +* *Comparison function*: most of the sorting algorithms in the library are comparison sorts. It means that the algorithm uses a comparison function to know the order of the elements and sort them accordingly; such a comparison function shall take two values and have a return type convertible to `bool`. The available sorting algorithms transform comparison functions on the fly so that some pointers to member functions can also be used as comparison functions, as if called with [`std::invoke`](https://en.cppreference.com/w/cpp/utility/functional/invoke). The default comparison function used by the sorting algorithms is [`std::less<>`](https://en.cppreference.com/w/cpp/utility/functional/less_void). Many sorters can take a comparison function as an additional parameter. For example, using `std::greater<>` instead of the default comparison function would sort a collection in descending order. cppsort::heap_sort(collection, std::greater<>{}); @@ -16,7 +16,7 @@ * *Fixed-size sorter*: [[fixed-size sorters]] are a special breed of sorters designed to sort a fixed number of values. While they try their best to be full-fledge sorters, they are definitely not full-fledge sorters and probably don't blend as well as one would like into the library. Their main advantage is that they can be more performant than regular sorters in some specific scenarios. -* *Iterator category*: the C++ standard defines [several categories of iterators](http://en.cppreference.com/w/cpp/iterator) such as forward iterators, bidirectional iterators or random-access iterators. The standard library uses [iterator tags](http://en.cppreference.com/w/cpp/iterator/iterator_tags) to document the category of an iterator. These categories are important since algorithms are designed to work with some categories of iterators and not with other categories, and those in this library are not different: in-place sorting needs at least forward iterators. You can use the [`iterator_category`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-traits#iterator_category) sorter trait to get the least constrained iterator category associated with a sorter. +* *Iterator category*: the C++ standard defines [several categories of iterators](https://en.cppreference.com/w/cpp/iterator) such as forward iterators, bidirectional iterators or random-access iterators. The standard library uses [iterator tags](https://en.cppreference.com/w/cpp/iterator/iterator_tags) to document the category of an iterator. These categories are important since algorithms are designed to work with some categories of iterators and not with other categories, and those in this library are not different: in-place sorting needs at least forward iterators. You can use the [`iterator_category`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-traits#iterator_category) sorter trait to get the least constrained iterator category associated with a sorter. using category = cppsort::iterator_category; @@ -26,7 +26,7 @@ auto max_inversion = cppsort::probe::dis(collection); -* *Projection*: some sorters accept a projection as an additional parameter. A projection is a unary function that allows to "view" the values of a collection differently. For example it may allow to sort a collection of values on a specific field. The available sorting algorithms transform projections on the fly so that pointers to member data can also be used as projections. Projections were pioneered by the [Adobe Source Libraries](http://stlab.adobe.com/) and appear in the C++20 [Constrained algorithms](https://en.cppreference.com/w/cpp/algorithm/ranges). +* *Projection*: some sorters accept a projection as an additional parameter. A projection is a unary function that allows to "view" the values of a collection differently. For example it may allow to sort a collection of values on a specific field. The available sorting algorithms transform projections on the fly so that pointers to member data can also be used as projections. Projections were pioneered by the [Adobe Source Libraries](https://stlab.adobe.com/) and appear in the C++20 [Constrained algorithms](https://en.cppreference.com/w/cpp/algorithm/ranges). struct wrapper { int value; }; std::vector collection = { /* ... */ }; diff --git a/docs/Miscellaneous-utilities.md b/docs/Miscellaneous-utilities.md index ec6658ca..3f72ce17 100644 --- a/docs/Miscellaneous-utilities.md +++ b/docs/Miscellaneous-utilities.md @@ -52,7 +52,7 @@ auto as_comparison(Function&& func) `as_function` is an utility function borrowed from Eric Niebler's [Ranges v3](https://github.com/ericniebler/range-v3) library. This function takes a parameter and does what it can to return an object callable with the usual function call syntax. It is notably useful to transform a pointer to member data into a function taking an instance of the class and returning the member. -To be more specific, `as_function` returns the passed object as is if it is already callable with the usual function call syntax, and uses [`std::mem_fn`](http://en.cppreference.com/w/cpp/utility/functional/mem_fn) to wrap the passed object otherwise. It is worth noting that when the original object is returned, its potential to be called in a `constexpr` context remains the same, which makes `as_function` superior to `std::invoke` in this regard (prior to C++20). +To be more specific, `as_function` returns the passed object as is if it is already callable with the usual function call syntax, and uses [`std::mem_fn`](https://en.cppreference.com/w/cpp/utility/functional/mem_fn) to wrap the passed object otherwise. It is worth noting that when the original object is returned, its potential to be called in a `constexpr` context remains the same, which makes `as_function` superior to `std::invoke` in this regard (prior to C++20). ```cpp struct wrapper { int foo; }; @@ -79,8 +79,8 @@ constexpr bool is_probably_branchless_comparison_v ``` This trait tells whether the comparison function `Compare` is likely to generate branchless code when comparing two instances of `T`. By default it considers that the following comparison functions are likely to be branchless: -* `std::less<>`, `std::ranges::less` and `std::less` for any `T` which satisfies [`std::is_arithmetic`](http://en.cppreference.com/w/cpp/types/is_arithmetic) -* `std::greater<>`, `std::ranges::greater` and `std::greater` for any `T` which satisfies [`std::is_arithmetic`](http://en.cppreference.com/w/cpp/types/is_arithmetic) +* `std::less<>`, `std::ranges::less` and `std::less` for any `T` which satisfies [`std::is_arithmetic`](https://en.cppreference.com/w/cpp/types/is_arithmetic) +* `std::greater<>`, `std::ranges::greater` and `std::greater` for any `T` which satisfies [`std::is_arithmetic`](https://en.cppreference.com/w/cpp/types/is_arithmetic) ```cpp template @@ -94,7 +94,7 @@ constexpr bool is_probably_branchless_projection_v This trait tells whether the projection function `Projection` is likely to generate branchless code when called with an instance of `T`. By default it considers that the following projection functions are likely to be branchless: * `cppsort::utility::identity` for any type * `std::identity` for any type (when available) -* Any type that satisfies [`std::is_member_function_pointer`](http://en.cppreference.com/w/cpp/types/is_member_function_pointer) provided it is called with an instance of the appropriate class +* Any type that satisfies [`std::is_member_function_pointer`](https://en.cppreference.com/w/cpp/types/is_member_function_pointer) provided it is called with an instance of the appropriate class These traits can be specialized for user-defined types. If one of the traits is specialized to consider that a user-defined type is likely to be branchless with a comparison/projection function, cv-qualified and reference-qualified versions of the same user-defined type will also be considered to produce branchless code when compared/projected with the same function. @@ -115,7 +115,7 @@ template struct fixed_buffer; ``` -This buffer provider allocates `N` elements on the stack. It uses [`std::array`](http://en.cppreference.com/w/cpp/container/array) behind the scenes, so it also allows buffers of size 0. The runtime size passed to the buffer at construction is discarded. +This buffer provider allocates `N` elements on the stack. It uses [`std::array`](https://en.cppreference.com/w/cpp/container/array) behind the scenes, so it also allows buffers of size 0. The runtime size passed to the buffer at construction is discarded. ```cpp template @@ -174,7 +174,7 @@ struct function_constant }; ``` -This utility is modeled after [`std::integral_constant`](http://en.cppreference.com/w/cpp/types/integral_constant), but is different in that it takes its parameter as `template`, and `operator()` calls the wrapped value instead of returning it. The goal is to store function pointers and pointer to members "for free": they are only "stored" as a template parameter, which allows `function_constant` to be an empty class. This has two main advantages: `function_constant` can benefit from *Empty Base Class Optimization* since it weights virtually nothing, and it won't need to be pushed on the stack when passed to a function, while the wrapped pointer would have been if passed unwrapped. Unless you are micro-optimizing some specific piece of code, you shouldn't need this class. +This utility is modeled after [`std::integral_constant`](https://en.cppreference.com/w/cpp/types/integral_constant), but is different in that it takes its parameter as `template`, and `operator()` calls the wrapped value instead of returning it. The goal is to store function pointers and pointer to members "for free": they are only "stored" as a template parameter, which allows `function_constant` to be an empty class. This has two main advantages: `function_constant` can benefit from *Empty Base Class Optimization* since it weights virtually nothing, and it won't need to be pushed on the stack when passed to a function, while the wrapped pointer would have been if passed unwrapped. Unless you are micro-optimizing some specific piece of code, you shouldn't need this class. `is_probably_branchless_comparison` and `is_probably_branchless_projection` will correspond to `std::true_type` if the wrapped `Function` also gives `std::true_type`. Moreover, you can even specialize these traits for specific `function_constant` instanciations if you need even more performance. @@ -212,7 +212,7 @@ auto iter_swap(Iterator lhs, Iterator rhs) ***WARNING:** `make_integer_range` and `make_index_range` are deprecated in version 1.8.0 and removed in version 2.0.0.* -The class template `make_integer_range` can be used wherever an [`std::integer_sequence`](http://en.cppreference.com/w/cpp/utility/integer_sequence) can be used. An `integer_range` takes a type template parameter that shall be an integer type, then three integer template parameters which correspond to the beginning of the range, the end of the range and the « step ». +The class template `make_integer_range` can be used wherever an [`std::integer_sequence`](https://en.cppreference.com/w/cpp/utility/integer_sequence) can be used. An `integer_range` takes a type template parameter that shall be an integer type, then three integer template parameters which correspond to the beginning of the range, the end of the range and the « step ». ```cpp template< @@ -241,7 +241,7 @@ using make_index_range = make_integer_range; #include ``` -`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`](http://en.cppreference.com/w/cpp/iterator/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. +`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`](https://en.cppreference.com/w/cpp/iterator/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. ### `static_const` @@ -259,6 +259,6 @@ namespace } ``` -You can read more about this instantiation pattern in [an article](http://ericniebler.com/2014/10/21/customization-point-design-in-c11-and-beyond/) by Eric Niebler. +You can read more about this instantiation pattern in [an article](https://ericniebler.com/2014/10/21/customization-point-design-in-c11-and-beyond/) by Eric Niebler. -*Warning: this header does not exist anymore in the C++17 branch; use [`inline` variables](http://en.cppreference.com/w/cpp/language/inline) instead.* +*Warning: this header does not exist anymore in the C++17 branch; use [`inline` variables](https://en.cppreference.com/w/cpp/language/inline) instead.* diff --git a/docs/Original-research.md b/docs/Original-research.md index 6fb3ceac..4274439d 100644 --- a/docs/Original-research.md +++ b/docs/Original-research.md @@ -190,7 +190,7 @@ Somehow Edelkamp and Weiß eventually [published a paper][quick-merge-sort-arxiv [better-sorting-networks]: https://etd.ohiolink.edu/!etd.send_file?accession=kent1239814529 [cycle-sort]: https://en.wikipedia.org/wiki/Cycle_sort [divide-sort-merge-strategy]: http://www.dtic.mil/dtic/tr/fulltext/u2/737270.pdf - [exact-sort]: http://www.geocities.ws/p356spt/ + [exact-sort]: https://www.geocities.ws/p356spt/ [indirect_adapter]: https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#indirect_adapter [morwenn-gist]: https://gist.github.com/Morwenn [mountain_sort]: https://github.com/Morwenn/mountain-sort diff --git a/docs/Sorter-facade.md b/docs/Sorter-facade.md index 06368774..70e0ca2b 100644 --- a/docs/Sorter-facade.md +++ b/docs/Sorter-facade.md @@ -43,7 +43,7 @@ template constexpr operator Ret(*)(Iterator, Iterator, Args...)() const; ``` -Note that the function pointer conversion syntax above is made up, but it allows to clearly highlight what it does while hiding the ugly `typedef`s needed for the syntax to be valid. In these signatures, `Ret` is an [`std::result_of_t`](http://en.cppreference.com/w/cpp/types/result_of) of the parameters (well, it is what you would expect it to be). The actual implementation is more verbose and redundant, but it allows to transform a sorter into a function pointer corresponding to any valid overload of `operator()`. +Note that the function pointer conversion syntax above is made up, but it allows to clearly highlight what it does while hiding the ugly `typedef`s needed for the syntax to be valid. In these signatures, `Ret` is an [`std::result_of_t`](https://en.cppreference.com/w/cpp/types/result_of) of the parameters (well, it is what you would expect it to be). The actual implementation is more verbose and redundant, but it allows to transform a sorter into a function pointer corresponding to any valid overload of `operator()`. Since C++17, these function pointer conversion operators are also `constexpr`. @@ -72,7 +72,7 @@ auto operator()(Iterator first, Iterator last, -> /* implementation-defined */; ``` -These overloads will generally forward the parameters to the corresponding `operator()` in the wrapped *sorter implementation*. It does some additional magic to forward `compare` and `projection` to the most suitable `operator()` overload in the *sorter implementation* and to complete the call with instances of [`std::less<>`](http://en.cppreference.com/w/cpp/utility/functional/less_void) and/or [`utility::identity`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#miscellaneous-function-objects) when additional parameters are needed. Basically, it ensures that everything can be done if `Sorter` has a single `operator()` taking a pair of iterators, a comparison function and a projection function. +These overloads will generally forward the parameters to the corresponding `operator()` in the wrapped *sorter implementation*. It does some additional magic to forward `compare` and `projection` to the most suitable `operator()` overload in the *sorter implementation* and to complete the call with instances of [`std::less<>`](https://en.cppreference.com/w/cpp/utility/functional/less_void) and/or [`utility::identity`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#miscellaneous-function-objects) when additional parameters are needed. Basically, it ensures that everything can be done if `Sorter` has a single `operator()` taking a pair of iterators, a comparison function and a projection function. Provided you have a sorting function with a standard iterator interface, creating the corresponding sorter becomes trivial thanks to `sorter_facade`. For instance, here is a simple sorter wrapping a [`selection_sort`](https://en.wikipedia.org/wiki/Selection_sort): @@ -119,7 +119,7 @@ auto operator()(Iterable&& iterable, Compare compare, Projection projection) con -> /* implementation-defined */; ``` -These overloads will generally forward the parameters to the corresponding `operator()` overloads in the wrapped *sorter implementation* if they exist, or try to call an equivalent `operator()` taking a pair of iterators in the wrapped sorter by using `utility::begin` and `utility::end` on the iterable to sort. It also does some additional magic to forward `compare` and `projection` to the most suitable `operator()` overload in `sorter` and to complete the call with instances of [`std::less<>`](http://en.cppreference.com/w/cpp/utility/functional/less_void) and/or [`utility::identity`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#miscellaneous-function-objects) when additional parameters are needed. Basically, it ensures that everything can be done if `Sorter` has a single `operator()` taking a pair of iterators, a comparison function and a projection function. +These overloads will generally forward the parameters to the corresponding `operator()` overloads in the wrapped *sorter implementation* if they exist, or try to call an equivalent `operator()` taking a pair of iterators in the wrapped sorter by using `utility::begin` and `utility::end` on the iterable to sort. It also does some additional magic to forward `compare` and `projection` to the most suitable `operator()` overload in `sorter` and to complete the call with instances of [`std::less<>`](https://en.cppreference.com/w/cpp/utility/functional/less_void) and/or [`utility::identity`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#miscellaneous-function-objects) when additional parameters are needed. Basically, it ensures that everything can be done if `Sorter` has a single `operator()` taking a pair of iterators, a comparison function and a projection function. It will always call the most suitable iterable `operator()` overload in the wrapped *sorter implementation* if there is one, and dispatch the call to an overload taking a pair of iterators when it cannot do otherwise. diff --git a/docs/Sorter-traits.md b/docs/Sorter-traits.md index d707242e..c2f69e0c 100644 --- a/docs/Sorter-traits.md +++ b/docs/Sorter-traits.md @@ -127,7 +127,7 @@ template using iterator_category = typename sorter_traits::iterator_category; ``` -Some tools need to know which category of iterators a sorting algorithm can work with. Therefore, a well-defined sorter shall provide one of the standard library [iterator tags](http://en.cppreference.com/w/cpp/iterator/iterator_tags) in order to document that. +Some tools need to know which category of iterators a sorting algorithm can work with. Therefore, a well-defined sorter shall provide one of the standard library [iterator tags](https://en.cppreference.com/w/cpp/iterator/iterator_tags) in order to document that. When a sorter is adapted so that it may be used with several categories of iterators, the resulting sorter's iterator category will correspond to the most permissive among the original sorters. For example, if an [`hybrid_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#hybrid_adapter) merges sorting algorithms with `std::forward_iterator_tag` and `std::random_access_iterator_tag`, the resulting sorter's category will be `std::forward_iterator_tag` since it is guaranteed to work with any iterable type which has *at least* forward iterators. @@ -142,7 +142,7 @@ constexpr bool is_always_stable_v = is_always_stable::value; ``` -This type trait is always either [`std::true_type`](http://en.cppreference.com/w/cpp/types/integral_constant) or `std::false_type` and tells whether a sorting algorithm is always [stable](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) or not. This information may be useful in some contexts, most notably to make sure that [`stable_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#stable_adapter) can use a stable sorter directly instead of artificially making it stable. +This type trait is always either [`std::true_type`](https://en.cppreference.com/w/cpp/types/integral_constant) or `std::false_type` and tells whether a sorting algorithm is always [stable](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) or not. This information may be useful in some contexts, most notably to make sure that [`stable_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#stable_adapter) can use a stable sorter directly instead of artificially making it stable. When a sorter adapter is used, the *resulting sorter* is stable if and only if its stability can be guaranteed and unstable otherwise, even when the *adapted sorter* may be stable (for example, [`self_sort_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#self_sort_adapter)'s `is_always_stable` is aliased to `std::false_type` since it is impossible to guarantee the stability of every `sort` method). @@ -208,6 +208,6 @@ struct fixed_sorter_traits; This class template can be specialized for any fixed-size sorter and exposes the following properties: -* `domain`: a specialization of [`std::index_sequence`](http://en.cppreference.com/w/cpp/utility/integer_sequence) containing all the sizes for which a specialization of the fixed-size sorter exists. If this trait isn't specified, it is assumed that the fixed-size sorter can be specialized for any value of `N`. +* `domain`: a specialization of [`std::index_sequence`](https://en.cppreference.com/w/cpp/utility/integer_sequence) containing all the sizes for which a specialization of the fixed-size sorter exists. If this trait isn't specified, it is assumed that the fixed-size sorter can be specialized for any value of `N`. * `iterator_category`: the category of iterators every specialization of the fixed-size sorter is guaranteed to work with. Individual specializations may work with less strict iterator categories. -* `is_always_stable`: an alias for [`std::true_type`](http://en.cppreference.com/w/cpp/types/integral_constant) if every specialization of the fixed-size sorter is guaranteed to always be stable, and `std::false_type` otherwise. \ No newline at end of file +* `is_always_stable`: an alias for [`std::true_type`](https://en.cppreference.com/w/cpp/types/integral_constant) if every specialization of the fixed-size sorter is guaranteed to always be stable, and `std::false_type` otherwise. \ No newline at end of file diff --git a/docs/Sorters.md b/docs/Sorters.md index 47641c83..994b3cfe 100644 --- a/docs/Sorters.md +++ b/docs/Sorters.md @@ -332,7 +332,7 @@ SplitSort is a [*Rem*-adaptive](https://github.com/Morwenn/cpp-sort/wiki/Measure #include ``` -Uses the standard library [`std::sort`](http://en.cppreference.com/w/cpp/algorithm/sort) to sort a collection. While the complexity guarantees are only partial in the standard, here is what's expected: +Uses the standard library [`std::sort`](https://en.cppreference.com/w/cpp/algorithm/sort) to sort a collection. While the complexity guarantees are only partial in the standard, here is what's expected: | Best | Average | Worst | Memory | Stable | Iterators | | ----------- | ----------- | ----------- | ----------- | ----------- | ------------- | @@ -340,14 +340,14 @@ Uses the standard library [`std::sort`](http://en.cppreference.com/w/cpp/algorit \* *`std::sort` is mandated by the standard to be O(n log n), but the libc++ implementation of the algorithm - despite non-trivial optimizations - [is still O(n²)](https://bugs.llvm.org/show_bug.cgi?id=20837). If you are using another standard library implementation then `std_sorter` should be O(n log n) for randon-access iterators, as expected.* -The adapter [`stable_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#stable_adapter) has an explicit specialization for `std_sorter` which calls [`std::stable_sort`](http://en.cppreference.com/w/cpp/algorithm/stable_sort) instead. Its complexity depends on whether it can allocate additional memory or not. While the complexity guarantees are only partial in the standard, here is what's expected: +The adapter [`stable_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#stable_adapter) has an explicit specialization for `std_sorter` which calls [`std::stable_sort`](https://en.cppreference.com/w/cpp/algorithm/stable_sort) instead. Its complexity depends on whether it can allocate additional memory or not. While the complexity guarantees are only partial in the standard, here is what's expected: | Best | Average | Worst | Memory | Stable | Iterators | | ----------- | ----------- | ----------- | ----------- | ----------- | ------------- | | n log n | n log n | n log n | n | Yes | Random-access | | n log² n | n log² n | n log² n | 1 | Yes | Random-access | -`std::sort` and `std::stable_sort` are likely not able to handle proxy iterators, therefore trying to use `std_sorter` with code that relies on proxy iterators (*e.g.* [`schwartz_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#schwartz_adapter)) is deemed to cause errors. However, some standard libraries provide overloads of standard algorithms for some containers; for example, libc++ has an overload of `std::sort` for bit iterators, which means that `std_sorter` could the the best choice to sort an [`std::vector`](http://en.cppreference.com/w/cpp/container/vector_bool). +`std::sort` and `std::stable_sort` are likely not able to handle proxy iterators, therefore trying to use `std_sorter` with code that relies on proxy iterators (*e.g.* [`schwartz_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#schwartz_adapter)) is deemed to cause errors. However, some standard libraries provide overloads of standard algorithms for some containers; for example, libc++ has an overload of `std::sort` for bit iterators, which means that `std_sorter` could the the best choice to sort an [`std::vector`](https://en.cppreference.com/w/cpp/container/vector_bool). This sorter can't throw `std::bad_alloc`. diff --git a/docs/Sorting-functions.md b/docs/Sorting-functions.md index 0783d54f..2e154216 100644 --- a/docs/Sorting-functions.md +++ b/docs/Sorting-functions.md @@ -40,7 +40,7 @@ auto sort(Iterator first, Iterator last, Compare compare, Projection projection) -> void; ``` -These overloads take either an `Iterable` or a pair of `Iterator`, and sort the corresponding collection in-place in ascending order using [`cppsort::default_sorter`](https://github.com/Morwenn/cpp-sort/wiki/Sorters#default_sorter) to perform the sort. If provided, the comparison function `compare` and the projection function `projection` are used instead of the default [`std::less<>`](http://en.cppreference.com/w/cpp/utility/functional/less_void) and [`utility::identity`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#miscellaneous-function-objects). +These overloads take either an `Iterable` or a pair of `Iterator`, and sort the corresponding collection in-place in ascending order using [`cppsort::default_sorter`](https://github.com/Morwenn/cpp-sort/wiki/Sorters#default_sorter) to perform the sort. If provided, the comparison function `compare` and the projection function `projection` are used instead of the default [`std::less<>`](https://en.cppreference.com/w/cpp/utility/functional/less_void) and [`utility::identity`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#miscellaneous-function-objects). ### Overload calling a user-provided sorter @@ -82,7 +82,7 @@ auto sort(const Sorter& sorter, Iterator first, Iterator last, -> decltype(auto); ``` -These overloads take either an `Iterable` or a pair of `Iterator`, and sort the corresponding collection in-place in ascending order using the given sorter. If provided, the comparison function `compare` and the projection function `projection` are used instead of the default [`std::less<>`](http://en.cppreference.com/w/cpp/utility/functional/less_void) and [`utility::identity`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#miscellaneous-function-objects). The returned value corresponds to the value returned by the sorter's `operator()` when it is given the other parameters. +These overloads take either an `Iterable` or a pair of `Iterator`, and sort the corresponding collection in-place in ascending order using the given sorter. If provided, the comparison function `compare` and the projection function `projection` are used instead of the default [`std::less<>`](https://en.cppreference.com/w/cpp/utility/functional/less_void) and [`utility::identity`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#miscellaneous-function-objects). The returned value corresponds to the value returned by the sorter's `operator()` when it is given the other parameters. Note that there is some heavy SFINAE wizardry happening to ensure that none of the `sort` overloads are ambiguous. This magic has been stripped from the documentation for clarity but may contribute to highly unreadable error messages. However, there is still some ambiguity left: the overload resolution might fail if `sort` is given an object that satisfies both the `Compare` and `Projection` concepts. This issue can we worked around with [`as_comparison` and `as_projection`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#as_comparison-and-as_projection) diff --git a/docs/Writing-a-bubble_sorter.md b/docs/Writing-a-bubble_sorter.md index 38528ebf..4df61e4e 100644 --- a/docs/Writing-a-bubble_sorter.md +++ b/docs/Writing-a-bubble_sorter.md @@ -163,7 +163,7 @@ auto bubble_sort(BidirectionalIterator first, BidirectionalIterator last, } ``` -Note that in C++17, it is preferred to use directly [`std::invoke`](http://en.cppreference.com/w/cpp/utility/functional/invoke) to call the comparison function instead of transforming it with `as_function`. +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 @@ -413,7 +413,7 @@ 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](http://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 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. ## Better error messages diff --git a/docs/Writing-a-container-aware-algorithm.md b/docs/Writing-a-container-aware-algorithm.md index 4cd64e7d..af612235 100644 --- a/docs/Writing-a-container-aware-algorithm.md +++ b/docs/Writing-a-container-aware-algorithm.md @@ -1,4 +1,4 @@ -Sometimes, iterators are not enough and you want to use the full abilities of containers to sort them, like O(1) insertion for [`std::list`](http://en.cppreference.com/w/cpp/container/list) and [`std::forward_list`](http://en.cppreference.com/w/cpp/container/forward_list). **cpp-sort** makes it possible to enhance sorters so that they can recognize specific containers and use a dedicated altered version of the sorting algorithm to sort the container thanks to [`container_aware_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#container_aware_adapter). +Sometimes, iterators are not enough and you want to use the full abilities of containers to sort them, like O(1) insertion for [`std::list`](https://en.cppreference.com/w/cpp/container/list) and [`std::forward_list`](https://en.cppreference.com/w/cpp/container/forward_list). **cpp-sort** makes it possible to enhance sorters so that they can recognize specific containers and use a dedicated altered version of the sorting algorithm to sort the container thanks to [`container_aware_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#container_aware_adapter). Let's get straight to example and enhance `selection_sort` for a custom list class to take advantage of the O(1) insertion. Here is the list implementation: diff --git a/docs/Writing-a-sorter.md b/docs/Writing-a-sorter.md index 3f68700e..53d0aa91 100644 --- a/docs/Writing-a-sorter.md +++ b/docs/Writing-a-sorter.md @@ -55,7 +55,7 @@ We just wrote what we call a *sorter implementation* and wrapped it into [`sorte Now, let's define a set of rules to apply when writing sorters. These rules don't *have* to be enforced, but enforcing them will ensure that a sorter will work smoothly with most tool available in this library. In this tutorial, every section will define a small set of rules instead of defining all of them at once without introducing the relevant concepts first. Fortunately, the simpler the sorter, the simpler the rules. -**Rule 1.1:** for any *sorter*, [`std::is_sorted`](http://en.cppreference.com/w/cpp/algorithm/is_sorted) called without a comparison function on the resulting range shall return `true` (note that this is not exactly true: floating point numbers are an example of types that will almost always cause problems). +**Rule 1.1:** for any *sorter*, [`std::is_sorted`](https://en.cppreference.com/w/cpp/algorithm/is_sorted) called without a comparison function on the resulting range shall return `true` (note that this is not exactly true: floating point numbers are an example of types that will almost always cause problems). **Rule 1.2:** a *sorter* shall be callable with either a pair of iterators or an iterable. @@ -67,7 +67,7 @@ Now, let's define a set of rules to apply when writing sorters. These rules don' ## Category of iterators -When writing a sorter, one of the most important things to consider is the [category of iterators](http://en.cppreference.com/w/cpp/iterator) it is meant to work with. It directly influences the kinds of collections that the sorter will be able to sort. Sorters implement in-place sorting algorithms, therefore they can only sort forward iterators or more specific types. **cpp-sort** does more than document the sorter category a sorter is supposed to work with: it actually embeds the information directly into the *sorter implementation* itself. If we take the `selection_sorter` from the previous section, we can document its properties as follows: +When writing a sorter, one of the most important things to consider is the [category of iterators](https://en.cppreference.com/w/cpp/iterator) it is meant to work with. It directly influences the kinds of collections that the sorter will be able to sort. Sorters implement in-place sorting algorithms, therefore they can only sort forward iterators or more specific types. **cpp-sort** does more than document the sorter category a sorter is supposed to work with: it actually embeds the information directly into the *sorter implementation* itself. If we take the `selection_sorter` from the previous section, we can document its properties as follows: ```cpp struct selection_sorter_impl @@ -83,7 +83,7 @@ struct selection_sorter_impl }; ``` -The standard library's [iterator tags](http://en.cppreference.com/w/cpp/iterator/iterator_tags) are used to document the iterator category supported by the sorter (stability is also documented but we'll come back to that later). It is a bit useful for error messages, but some other tools from the library rely of this information. For example [`hybrid_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#hybrid_adapter) can take several sorters with different iterator categories and generate a new sorter that will call the appropriate sorter depending on the iterator category of the passed collection: +The standard library's [iterator tags](https://en.cppreference.com/w/cpp/iterator/iterator_tags) are used to document the iterator category supported by the sorter (stability is also documented but we'll come back to that later). It is a bit useful for error messages, but some other tools from the library rely of this information. For example [`hybrid_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#hybrid_adapter) can take several sorters with different iterator categories and generate a new sorter that will call the appropriate sorter depending on the iterator category of the passed collection: ```cpp using sorter = cppsort::hybrid_adapter< @@ -121,7 +121,7 @@ As you can see, the iterator category supported by a given sorter is not only th ## Comparison sorters -Most sorting algorithms are [comparison sorts](https://en.wikipedia.org/wiki/Comparison_sort). It means that, to sort the elements of a collection, they repeatedly use a comparison function that returns whether two elements are already in order. The standard library's [`std::sort`](http://en.cppreference.com/w/cpp/algorithm/sort) implicitly uses an ADL-found `operator<` to compare two elements, but it also provides an overload which takes a user-provided comparison function to compare two elements. **cpp-sort** loosely follows this design (it defaults to `std::less<>` instead of `operator<`) and allows its sorters to take an additional parameter for user-provided comparison functions. Let's write a *sorter implementation* to wrap the three-parameter overload of `std::sort`: +Most sorting algorithms are [comparison sorts](https://en.wikipedia.org/wiki/Comparison_sort). It means that, to sort the elements of a collection, they repeatedly use a comparison function that returns whether two elements are already in order. The standard library's [`std::sort`](https://en.cppreference.com/w/cpp/algorithm/sort) implicitly uses an ADL-found `operator<` to compare two elements, but it also provides an overload which takes a user-provided comparison function to compare two elements. **cpp-sort** loosely follows this design (it defaults to `std::less<>` instead of `operator<`) and allows its sorters to take an additional parameter for user-provided comparison functions. Let's write a *sorter implementation* to wrap the three-parameter overload of `std::sort`: ```cpp struct std_sorter_impl @@ -147,9 +147,9 @@ struct std_sorter: {}; ``` -Compared to the previous `selection_sorter_impl`, the only things we had to add was a template parameter defaulted to [`std::less<>`](http://en.cppreference.com/w/cpp/utility/functional/less_void) and a default-contructed (when not provided) parameter to `operator()`. As usual, [`sorter_facade`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-facade) generates a bunch of additional features: it still adds the overload of `operator()` taking a single iterable, but also adds an overload taking an iterable and a comparison function. Basically, it ensures that you always only have to provide a single overload of `operator()`, and generates all the other ones as well as all the corresponding conversions to function pointers. +Compared to the previous `selection_sorter_impl`, the only things we had to add was a template parameter defaulted to [`std::less<>`](https://en.cppreference.com/w/cpp/utility/functional/less_void) and a default-contructed (when not provided) parameter to `operator()`. As usual, [`sorter_facade`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-facade) generates a bunch of additional features: it still adds the overload of `operator()` taking a single iterable, but also adds an overload taking an iterable and a comparison function. Basically, it ensures that you always only have to provide a single overload of `operator()`, and generates all the other ones as well as all the corresponding conversions to function pointers. -This kind of comparison sorters help to compare things that don't have an overloaded `operator<` or to compare things differently. For example, passing [`std::greater<>`](http://en.cppreference.com/w/cpp/utility/functional/greater_void) to a sorter instead of `std::less<>` sorts a collection in descending order: +This kind of comparison sorters help to compare things that don't have an overloaded `operator<` or to compare things differently. For example, passing [`std::greater<>`](https://en.cppreference.com/w/cpp/utility/functional/greater_void) to a sorter instead of `std::less<>` sorts a collection in descending order: ```cpp // Sort collection in reverse order with std::sort @@ -162,7 +162,7 @@ The rules for *comparison sorters* are but an extension to the rules defined for **Rule 3.1:** a *comparison sorter* is also a *sorter*, which means that it shall be called without a comparison function and shall obey all the rules defined for regular *sorters*. -**Rule 3.2:** for any *comparison sorter* called with a specific comparison function, [`std::is_sorted`](http://en.cppreference.com/w/cpp/algorithm/is_sorted) called with the same comparison function on the resulting collection shall return `true`. +**Rule 3.2:** for any *comparison sorter* called with a specific comparison function, [`std::is_sorted`](https://en.cppreference.com/w/cpp/algorithm/is_sorted) called with the same comparison function on the resulting collection shall return `true`. **Rule 3.3:** calling a *comparison sorter* with `std::less<>` or without a comparison function shall be strictly equivalent: calling `std::is_sorted` without a comparison function on the resulting collection shall return `true`. @@ -172,7 +172,7 @@ The rules for *comparison sorters* are but an extension to the rules defined for ## Handling projections -The [Ranges TS](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4560.pdf) introduces the notion of callable *projections*, borrowed from the [Adobe Source Libraries](http://stlab.adobe.com/). A projection is a callable object that can be passed to an algorithm so that it "views" the values to be compared differently. For example, [`std::negate<>`](http://en.cppreference.com/w/cpp/utility/functional/negate_void) could be used to sort a collection of integers in descending order. Let's assume that our `selection_sort` algorithm from a while has been given a fourth parameter to handle projections; here is the corresponding *sorter implementation*: +The [Ranges TS](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4560.pdf) introduces the notion of callable *projections*, borrowed from the [Adobe Source Libraries](https://stlab.adobe.com/). A projection is a callable object that can be passed to an algorithm so that it "views" the values to be compared differently. For example, [`std::negate<>`](https://en.cppreference.com/w/cpp/utility/functional/negate_void) could be used to sort a collection of integers in descending order. Let's assume that our `selection_sort` algorithm from a while has been given a fourth parameter to handle projections; here is the corresponding *sorter implementation*: ```cpp struct selection_sorter_impl @@ -245,7 +245,7 @@ The general rules for *projection sorters* are really close to the ones for *com **Rule 4.2:** a sorter can be both a *comparison sorter* and a *projection sorter* at once. Such a sorter shall also obey all the rules defined for *sorters*, for *comparison sorters* and for *projection sorters*. -**Rule 4.3:** for any *projection sorter* called with a specific projection function, [`std::is_sorted`](http://en.cppreference.com/w/cpp/algorithm/is_sorted) called with `std::less<>` and the same projection function on the resulting collection shall return `true`. If the *projection sorter* is also a *comparison sorter*, for any such sorter called with a specific pair of comparison and projection function, `std::is_sorted` called with the same pair of functions on the resulting collection shall return `true`. +**Rule 4.3:** for any *projection sorter* called with a specific projection function, [`std::is_sorted`](https://en.cppreference.com/w/cpp/algorithm/is_sorted) called with `std::less<>` and the same projection function on the resulting collection shall return `true`. If the *projection sorter* is also a *comparison sorter*, for any such sorter called with a specific pair of comparison and projection function, `std::is_sorted` called with the same pair of functions on the resulting collection shall return `true`. **Rule 4.4:** calling a *projection sorter* with [`utility::identity`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#miscellaneous-function-objects) or without a projection function shall be strictly equivalent: calling `std::is_sorted` without a comparison function and without a projection function on the resulting collection shall return `true`. If the *projection sorter* is also a *comparison sorter*, calling such a sorter with any valid combination of `std::less<>` and `utility::identity`, and calling it without any additional function should be strictly equivalent. @@ -307,7 +307,7 @@ struct counting_sorter_impl }; ``` -With such an implementation, this sorter satisfies the *comparison sorter* concept when given an instance of `std::greater<>` without breaking any of the rules defined in the previous sections. Now it may seem a bit unfair for `std::less<>`... but actually [`sorter_facade`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-facade) automagically generates several `operator()` overloads taking `std::less<>` when the provided *sorter implementation* doesn't handle it natively. Note that even though this section is about non-comparison sorters, the same applies to non-projection sorters (you could provide a specific overload for [`std::negate<>`](http://en.cppreference.com/w/cpp/utility/functional/negate_void) for a descending sort too), and `sorter_facade` would provide equivalent `operator()` overloads taking [`utility::identity`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#miscellaneous-function-objects) for *sorter implementations* that cannot handle it natively). +With such an implementation, this sorter satisfies the *comparison sorter* concept when given an instance of `std::greater<>` without breaking any of the rules defined in the previous sections. Now it may seem a bit unfair for `std::less<>`... but actually [`sorter_facade`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-facade) automagically generates several `operator()` overloads taking `std::less<>` when the provided *sorter implementation* doesn't handle it natively. Note that even though this section is about non-comparison sorters, the same applies to non-projection sorters (you could provide a specific overload for [`std::negate<>`](https://en.cppreference.com/w/cpp/utility/functional/negate_void) for a descending sort too), and `sorter_facade` would provide equivalent `operator()` overloads taking [`utility::identity`](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#miscellaneous-function-objects) for *sorter implementations* that cannot handle it natively). The most beautiful thing in my opinion is that no new rule is needed to support that model. All the rules previously defined guarantee that these specific overloads using standard function object as tags work. The only advice I can give is to try to use the most standard function objects as tags, or at least the ones that are the most likely to be used for the specific task. Since **cpp-sort** is heavily based on modern C++ features, it is designed to only work with the `void` specializations of the standard function objects from ``. @@ -368,7 +368,7 @@ While type-specific sorters are, by their very nature, unable to generically han ## Stability -A sorting algorithm is said to be [stable](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) if it preserves the relative order of equivalent elements. **cpp-sort** documents the stability of every sorter by giving them an `is_always_stable` type aliasing a boolean specialization of `std::integer_constant`. This information should be accessed via [`sorter_traits`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-traits#sorter_traits) or via the more specific [`is_always_stable`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-traits#is_always_stable) type alias. The stability of a sorter is always either [`std::true_type`](http://en.cppreference.com/w/cpp/types/integral_constant) or `std::false_type`. +A sorting algorithm is said to be [stable](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) if it preserves the relative order of equivalent elements. **cpp-sort** documents the stability of every sorter by giving them an `is_always_stable` type aliasing a boolean specialization of `std::integer_constant`. This information should be accessed via [`sorter_traits`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-traits#sorter_traits) or via the more specific [`is_always_stable`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-traits#is_always_stable) type alias. The stability of a sorter is always either [`std::true_type`](https://en.cppreference.com/w/cpp/types/integral_constant) or `std::false_type`. ```cpp using stability = cppsort::is_always_stable; @@ -533,7 +533,7 @@ namespace cppsort If `domain` does not exist in the `fixed_sorter_traits` specialization, it means that every specialization of the fixed-size sorter is a valid sorter. -Using the specializations of a fixed-size sorter by hand is not the sweetest thing either; that is why the library provides the fixed-size sorter adapter [`small_array_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#small_array_adapter) which takes a whole *fixed-size sorter* and almost transforms it into a sorter (the resulting class doesn't handle pairs of iterators). The resulting object, when given an instance of [`std::array`](http://en.cppreference.com/w/cpp/container/array) or a fixed-size C array, will sort it in-place with the specialization corresponding to the size of the passed array. To transform that into a full sorter able to handle anything, one needs to aggregate it with another sorter into a `hybrid_adapter`: +Using the specializations of a fixed-size sorter by hand is not the sweetest thing either; that is why the library provides the fixed-size sorter adapter [`small_array_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#small_array_adapter) which takes a whole *fixed-size sorter* and almost transforms it into a sorter (the resulting class doesn't handle pairs of iterators). The resulting object, when given an instance of [`std::array`](https://en.cppreference.com/w/cpp/container/array) or a fixed-size C array, will sort it in-place with the specialization corresponding to the size of the passed array. To transform that into a full sorter able to handle anything, one needs to aggregate it with another sorter into a `hybrid_adapter`: ```cpp using sorter = cppsort::hybrid_adapter< From 8b3bfb687ae98aa769fe80d2ceb4c1d736d7b4df Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 21 Dec 2020 14:34:58 +0100 Subject: [PATCH 29/90] typo in comment [ci skip] --- include/cpp-sort/detail/fixed_size_list.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/cpp-sort/detail/fixed_size_list.h b/include/cpp-sort/detail/fixed_size_list.h index 93899a1e..52fb9424 100644 --- a/include/cpp-sort/detail/fixed_size_list.h +++ b/include/cpp-sort/detail/fixed_size_list.h @@ -50,7 +50,7 @@ namespace detail // Inhibit construction of the node with a value union { T value; }; - // Pointers to th previous and next nodes + // Pointers to the previous and next nodes fixed_size_list_node* prev; fixed_size_list_node* next; }; From 237936f9b42503eaf586c39220596a37af443512 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 21 Dec 2020 14:36:50 +0100 Subject: [PATCH 30/90] Add operator== to generic_stable_wrapper --- testsuite/testing-tools/wrapper.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/testsuite/testing-tools/wrapper.h b/testsuite/testing-tools/wrapper.h index a3efab1c..8ed89a18 100644 --- a/testsuite/testing-tools/wrapper.h +++ b/testsuite/testing-tools/wrapper.h @@ -95,7 +95,14 @@ struct generic_stable_wrapper return *this; } - friend auto operator<(const generic_stable_wrapper& lhs, const generic_stable_wrapper& rhs) + friend constexpr auto operator==(const generic_stable_wrapper& lhs, const generic_stable_wrapper& rhs) + -> bool + { + return lhs.value == rhs.value + && lhs.order == rhs.order; + } + + friend constexpr auto operator<(const generic_stable_wrapper& lhs, const generic_stable_wrapper& rhs) -> bool { if (lhs.value < rhs.value) { From 6f9738dfe5c7a1d7464f4a61c92f9b8e003283af Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 21 Dec 2020 17:14:14 +0100 Subject: [PATCH 31/90] Improvements to benchmarks and tools Mostly quality-of-life changes that allow me to change them faster when I need to prototype, debug and test components more easily. [ci skip] --- benchmarks/benchmarking-tools/distributions.h | 546 +++++++++--------- benchmarks/errorbar-plot/bench.cpp | 2 +- benchmarks/inversions/inv-bench.cpp | 4 +- benchmarks/patterns/bars.py | 21 +- benchmarks/patterns/bench.cpp | 26 +- benchmarks/small-array/benchmark.cpp | 20 +- tools/test_failing_sorter.cpp | 18 +- 7 files changed, 323 insertions(+), 314 deletions(-) diff --git a/benchmarks/benchmarking-tools/distributions.h b/benchmarks/benchmarking-tools/distributions.h index 24ef901f..d08f10d8 100644 --- a/benchmarks/benchmarking-tools/distributions.h +++ b/benchmarks/benchmarking-tools/distributions.h @@ -3,9 +3,8 @@ * SPDX-License-Identifier: MIT */ #include +#include #include -#include -#include #include #include #include @@ -21,350 +20,353 @@ thread_local std::mt19937_64 distributions_prng(std::time(nullptr)); // Distributions for benchmarks // // Distributions are function objects used to fill the -// collections to be sorter before benchmarking the sorting +// collections to be sorted before benchmarking the sorting // algorithms. These distributions allow to check how a // sorting algorithm behaves with common patterns found in -// actual data sets. -// +// real-life data sets, or with patterns known to adversely +// affect some classes of sorting algorithms. -template -struct base_distribution +namespace dist { - template - using fptr_t = void(*)(OutputIterator, std::size_t); + template + struct base_distribution + { + template + using fptr_t = void(*)(OutputIterator, std::size_t); - template - using fptr_proj_t = void(*)(OutputIterator, std::size_t, Projection); + template + using fptr_proj_t = void(*)(OutputIterator, std::size_t, Projection); - template - operator fptr_t() const - { - return [](OutputIterator out, std::size_t size) + template + operator fptr_t() const { - return Derived{}(out, size); - }; - } + return [](OutputIterator out, std::size_t size) + { + return Derived{}(out, size); + }; + } - template - operator fptr_proj_t() const - { - return [](OutputIterator out, std::size_t size, Projection projection) + template + operator fptr_proj_t() const { - return Derived{}(out, size, std::move(projection)); - }; - } -}; + return [](OutputIterator out, std::size_t size, Projection projection) + { + return Derived{}(out, size, std::move(projection)); + }; + } + }; -struct shuffled: - base_distribution -{ - template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const - -> void + struct shuffled: + base_distribution { - std::vector vec; - for (std::size_t i = 0 ; i < size ; ++i) { - vec.emplace_back(i); - } - std::shuffle(std::begin(vec), std::end(vec), distributions_prng); + template + auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + -> void + { + std::vector vec; + for (std::size_t i = 0 ; i < size ; ++i) { + vec.emplace_back(i); + } + std::shuffle(vec.begin(), vec.end(), distributions_prng); - auto&& proj = cppsort::utility::as_function(projection); - std::transform(std::begin(vec), std::end(vec), out, proj); - } + auto&& proj = cppsort::utility::as_function(projection); + std::transform(vec.begin(), vec.end(), out, proj); + } - static constexpr const char* output = "shuffled.txt"; -}; + static constexpr const char* output = "shuffled.txt"; + }; -struct shuffled_16_values: - base_distribution -{ - template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const - -> void + struct shuffled_16_values: + base_distribution { - std::vector vec; - for (std::size_t i = 0 ; i < size ; ++i) { - vec.emplace_back(i % 16); - } - std::shuffle(std::begin(vec), std::end(vec), distributions_prng); + template + auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + -> void + { + std::vector vec; + for (std::size_t i = 0 ; i < size ; ++i) { + vec.emplace_back(i % 16); + } + std::shuffle(vec.begin(), vec.end(), distributions_prng); - auto&& proj = cppsort::utility::as_function(projection); - std::transform(std::begin(vec), std::end(vec), out, proj); - } + auto&& proj = cppsort::utility::as_function(projection); + std::transform(vec.begin(), vec.end(), out, proj); + } - static constexpr const char* output = "shuffled.txt"; -}; + static constexpr const char* output = "shuffled.txt"; + }; -struct all_equal: - base_distribution -{ - template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const - -> void + struct all_equal: + base_distribution { - auto&& proj = cppsort::utility::as_function(projection); - for (std::size_t i = 0 ; i < size ; ++i) { - *out++ = proj(0); + template + auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + -> void + { + auto&& proj = cppsort::utility::as_function(projection); + for (std::size_t i = 0 ; i < size ; ++i) { + *out++ = proj(0); + } } - } - static constexpr const char* output = "all_equal.txt"; -}; + static constexpr const char* output = "all_equal.txt"; + }; -struct ascending: - base_distribution -{ - template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const - -> void + struct ascending: + base_distribution { - auto&& proj = cppsort::utility::as_function(projection); - for (std::size_t i = 0 ; i < size ; ++i) { - *out++ = proj(i); + template + auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + -> void + { + auto&& proj = cppsort::utility::as_function(projection); + for (std::size_t i = 0 ; i < size ; ++i) { + *out++ = proj(i); + } } - } - static constexpr const char* output = "ascending.txt"; -}; + static constexpr const char* output = "ascending.txt"; + }; -struct descending: - base_distribution -{ - template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const - -> void + struct descending: + base_distribution { - auto&& proj = cppsort::utility::as_function(projection); - while (size--) { - *out++ = proj(size); + template + auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + -> void + { + auto&& proj = cppsort::utility::as_function(projection); + while (size--) { + *out++ = proj(size); + } } - } - static constexpr const char* output = "descending.txt"; -}; + static constexpr const char* output = "descending.txt"; + }; -struct pipe_organ: - base_distribution -{ - template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const - -> void + struct pipe_organ: + base_distribution { - auto&& proj = cppsort::utility::as_function(projection); - for (std::size_t i = 0 ; i < size / 2 ; ++i) { - *out++ = proj(i); - } - for (std::size_t i = size / 2 ; i < size ; ++i) { - *out++ = proj(size - i); + template + auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + -> void + { + auto&& proj = cppsort::utility::as_function(projection); + for (std::size_t i = 0 ; i < size / 2 ; ++i) { + *out++ = proj(i); + } + for (std::size_t i = size / 2 ; i < size ; ++i) { + *out++ = proj(size - i); + } } - } - static constexpr const char* output = "pipe_organ.txt"; -}; + static constexpr const char* output = "pipe_organ.txt"; + }; -struct push_front: - base_distribution -{ - template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const - -> void + struct push_front: + base_distribution { - auto&& proj = cppsort::utility::as_function(projection); - if (size > 0) { - for (std::size_t i = 0 ; i < size - 1 ; ++i) { - *out++ = proj(i); + template + auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + -> void + { + auto&& proj = cppsort::utility::as_function(projection); + if (size > 0) { + for (std::size_t i = 0 ; i < size - 1 ; ++i) { + *out++ = proj(i); + } + *out = proj(0); } - *out = proj(0); } - } - static constexpr const char* output = "push_front.txt"; -}; + static constexpr const char* output = "push_front.txt"; + }; -struct push_middle: - base_distribution -{ - template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const - -> void + struct push_middle: + base_distribution { - auto&& proj = cppsort::utility::as_function(projection); - if (size > 0) { - for (std::size_t i = 0 ; i < size ; ++i) { - if (i != size / 2) { - *out++ = proj(i); + template + auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + -> void + { + auto&& proj = cppsort::utility::as_function(projection); + if (size > 0) { + for (std::size_t i = 0 ; i < size ; ++i) { + if (i != size / 2) { + *out++ = proj(i); + } } + *out = proj(size / 2); } - *out = proj(size / 2); } - } - static constexpr const char* output = "push_middle.txt"; -}; + static constexpr const char* output = "push_middle.txt"; + }; -struct ascending_sawtooth: - base_distribution -{ - template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const - -> void + struct ascending_sawtooth: + base_distribution { - auto&& proj = cppsort::utility::as_function(projection); - std::size_t limit = size / cppsort::detail::log2(size) + 50; - for (std::size_t i = 0 ; i < size ; ++i) { - *out++ = proj(i % limit); + template + auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + -> void + { + auto&& proj = cppsort::utility::as_function(projection); + std::size_t limit = size / cppsort::detail::log2(size) + 50; + for (std::size_t i = 0 ; i < size ; ++i) { + *out++ = proj(i % limit); + } } - } - static constexpr const char* output = "ascending_sawtooth.txt"; -}; + static constexpr const char* output = "ascending_sawtooth.txt"; + }; -struct ascending_sawtooth_bad: - base_distribution -{ - template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const - -> void + struct ascending_sawtooth_bad: + base_distribution { - auto&& proj = cppsort::utility::as_function(projection); - std::size_t limit = size / cppsort::detail::log2(size) - 50; - for (std::size_t i = 0 ; i < size ; ++i) { - *out++ = proj(i % limit); + template + auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + -> void + { + auto&& proj = cppsort::utility::as_function(projection); + std::size_t limit = size / cppsort::detail::log2(size) - 50; + for (std::size_t i = 0 ; i < size ; ++i) { + *out++ = proj(i % limit); + } } - } - static constexpr const char* output = "ascending_sawtooth.txt"; -}; + static constexpr const char* output = "ascending_sawtooth.txt"; + }; -struct descending_sawtooth: - base_distribution -{ - template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const - -> void + struct descending_sawtooth: + base_distribution { - auto&& proj = cppsort::utility::as_function(projection); - std::size_t limit = size / cppsort::detail::log2(size) + 50; - while (size--) { - *out++ = proj(size % limit); + template + auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + -> void + { + auto&& proj = cppsort::utility::as_function(projection); + std::size_t limit = size / cppsort::detail::log2(size) + 50; + while (size--) { + *out++ = proj(size % limit); + } } - } - static constexpr const char* output = "descending_sawtooth.txt"; -}; + static constexpr const char* output = "descending_sawtooth.txt"; + }; -struct descending_sawtooth_bad: - base_distribution -{ - template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const - -> void + struct descending_sawtooth_bad: + base_distribution { - auto&& proj = cppsort::utility::as_function(projection); - std::size_t limit = size / cppsort::detail::log2(size) - 50; - while (size--) { - *out++ = proj(size % limit); + template + auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + -> void + { + auto&& proj = cppsort::utility::as_function(projection); + std::size_t limit = size / cppsort::detail::log2(size) - 50; + while (size--) { + *out++ = proj(size % limit); + } } - } - static constexpr const char* output = "descending_sawtooth.txt"; -}; + static constexpr const char* output = "descending_sawtooth.txt"; + }; -struct alternating: - base_distribution -{ - template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const - -> void + struct alternating: + base_distribution { - auto&& proj = cppsort::utility::as_function(projection); - for (std::size_t i = 0 ; i < size ; ++i) { - *out++ = proj((i % 2) ? i : -i); + template + auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + -> void + { + auto&& proj = cppsort::utility::as_function(projection); + for (std::size_t i = 0 ; i < size ; ++i) { + *out++ = proj((i % 2) ? i : -i); + } } - } - static constexpr const char* output = "alternating.txt"; -}; + static constexpr const char* output = "alternating.txt"; + }; -struct alternating_16_values: - base_distribution -{ - template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const - -> void + struct alternating_16_values: + base_distribution { - auto&& proj = cppsort::utility::as_function(projection); - for (std::size_t i = 0 ; i < size ; ++i) { - *out++ = proj((i % 2) ? i % 16 : -(i % 16)); + template + auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + -> void + { + auto&& proj = cppsort::utility::as_function(projection); + for (std::size_t i = 0 ; i < size ; ++i) { + *out++ = proj((i % 2) ? i % 16 : -(i % 16)); + } } - } - static constexpr const char* output = "alternating_16_values.txt"; -}; + static constexpr const char* output = "alternating_16_values.txt"; + }; -struct inversions: - base_distribution -{ - // Percentage of chances that an "out-of-place" value - // is produced for each position, the goal is to test - // Inv-adaptive algorithms - double factor; - - constexpr explicit inversions(double factor) noexcept: - factor(factor) - {} - - template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const - -> void + struct inversions: + base_distribution { - auto&& proj = cppsort::utility::as_function(projection); - - // Generate a percentage of error - std::uniform_real_distribution percent_dis(0.0, 1.0); - // Generate a random value - std::uniform_int_distribution value_dis(0, size - 1); - - for (std::size_t i = 0 ; i < size ; ++i) { - if (percent_dis(distributions_prng) < factor) { - *out++ = value_dis(distributions_prng); - } else { - *out++ = i; + // Percentage of chances that an "out-of-place" value + // is produced for each position, the goal is to test + // Inv-adaptive algorithms + double factor; + + constexpr explicit inversions(double factor) noexcept: + factor(factor) + {} + + template + auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + -> void + { + auto&& proj = cppsort::utility::as_function(projection); + + // Generate a percentage of error + std::uniform_real_distribution percent_dis(0.0, 1.0); + // Generate a random value + std::uniform_int_distribution value_dis(0, size - 1); + + for (std::size_t i = 0 ; i < size ; ++i) { + if (percent_dis(distributions_prng) < factor) { + *out++ = value_dis(distributions_prng); + } else { + *out++ = i; + } } } - } - static constexpr const char* output = "inversions.txt"; -}; + static constexpr const char* output = "inversions.txt"; + }; -struct vergesort_killer: - base_distribution -{ - template - auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const - -> void + struct vergesort_killer: + base_distribution { - // WARNING: not for small collections, mostly because I'm lazy... - - const auto size_run = size / cppsort::detail::log2(size); - auto desc = descending{}; - auto killer = pipe_organ{}; - - auto size_output_left = size; - while (true) { - killer(out, size_run - 50, projection); - size_output_left -= size_run - 50; - if (size_output_left < size_run + 50) break; - desc(out, size_run + 50, projection); - size_output_left -= size_run + 50; - if (size_output_left < size_run - 50) break; - }; - - // Just in case - if (size_output_left) { - shuffled{}(out, size_output_left, projection); + template + auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + -> void + { + // WARNING: not for small collections, mostly because I'm lazy... + + const auto size_run = size / cppsort::detail::log2(size); + auto desc = descending{}; + auto killer = pipe_organ{}; + + auto size_output_left = size; + while (true) { + killer(out, size_run - 50, projection); + size_output_left -= size_run - 50; + if (size_output_left < size_run + 50) break; + desc(out, size_run + 50, projection); + size_output_left -= size_run + 50; + if (size_output_left < size_run - 50) break; + }; + + // Just in case + if (size_output_left) { + shuffled{}(out, size_output_left, projection); + } } - } - static constexpr const char* output = "vergesort_killer.txt"; -}; + static constexpr const char* output = "vergesort_killer.txt"; + }; +} diff --git a/benchmarks/errorbar-plot/bench.cpp b/benchmarks/errorbar-plot/bench.cpp index 613bb0a3..0604a614 100644 --- a/benchmarks/errorbar-plot/bench.cpp +++ b/benchmarks/errorbar-plot/bench.cpp @@ -40,7 +40,7 @@ std::pair sorts[] = { }; // Distribution to benchmark against -auto distribution = shuffled{}; +auto distribution = dist::shuffled{}; // Sizes of the collections to sort std::uint64_t size_min = 1u << 1; diff --git a/benchmarks/inversions/inv-bench.cpp b/benchmarks/inversions/inv-bench.cpp index 38d6be33..a1df50d6 100644 --- a/benchmarks/inversions/inv-bench.cpp +++ b/benchmarks/inversions/inv-bench.cpp @@ -81,7 +81,7 @@ int main(int argc, char* argv[]) for (int idx = 0 ; idx <= 100 ; ++idx) { double factor = 0.01 * idx; - auto dist = inversions(factor); + auto distribution = dist::inversions(factor); std::vector cycles; @@ -90,7 +90,7 @@ int main(int argc, char* argv[]) while (std::chrono::duration_cast(total_end - total_start) < max_run_time && cycles.size() < max_runs_per_size) { collection_t collection; - dist(std::back_inserter(collection), size); + distribution(std::back_inserter(collection), size); std::uint64_t start = rdtsc(); sort.second(collection); std::uint64_t end = rdtsc(); diff --git a/benchmarks/patterns/bars.py b/benchmarks/patterns/bars.py index 40fe89e7..e290461c 100644 --- a/benchmarks/patterns/bars.py +++ b/benchmarks/patterns/bars.py @@ -50,10 +50,18 @@ def main(): "ascending_sawtooth": "Ascending sawtooth", "descending_sawtooth": "Descending sawtooth", "alternating": "Alternating", - "alternating_16_values": "Alternating (16 values)" + "alternating_16_values": "Alternating (16 values)", } - sort_order = ["heap_sort", "pdq_sort", "quick_sort", "spread_sort", "std_sort", "verge_sort"] + # Algorithm results will be displayed in this order + algos = [ + "heap_sort", + "pdq_sort", + "quick_sort", + "spread_sort", + "std_sort", + "verge_sort", + ] root = pathlib.Path(args.root) for file in root.iterdir(): @@ -72,7 +80,7 @@ def main(): # Choose the colour palette and markers to use if args.use_alt_palette: # That one has the advantage of being infinite - palette = pyplot.cm.rainbow(numpy.linspace(0, 1, len(sort_order))) + palette = pyplot.cm.rainbow(numpy.linspace(0, 1, len(algos))) else: # Colorblind-friendly palette (https://gist.github.com/thriveth/8560036) palette = ['#377eb8', '#ff7f00', '#4daf4a', @@ -92,12 +100,9 @@ def main(): "Ascending sawtooth", "Descending sawtooth", "Alternating", - "Alternating (16 values)" + "Alternating (16 values)", ) - algos = tuple(data[size]["Shuffled"].keys()) - algos = tuple(sorted(algos, key=lambda a: sort_order.index(a))) - groupnames = distributions groupsize = len(algos) groups = [[data[size][distribution][algo] for algo in algos] for distribution in distributions] @@ -132,7 +137,7 @@ def main(): ax.autoscale_view() pyplot.ylim(pyplot.ylim()[0] + 1, pyplot.ylim()[1] - 1) - pyplot.title("Sorting $10^{}$ elements".format(round(math.log(size, 10)))) + pyplot.title("Sorting a std::deque with $10^{}$ elements".format(round(math.log(size, 10)))) pyplot.legend(loc="best") figure = pyplot.gcf() diff --git a/benchmarks/patterns/bench.cpp b/benchmarks/patterns/bench.cpp index c0dd2a9c..dde27d68 100644 --- a/benchmarks/patterns/bench.cpp +++ b/benchmarks/patterns/bench.cpp @@ -60,18 +60,18 @@ int main() >; std::pair distributions[] = { - { "shuffled", shuffled() }, - { "shuffled_16_values", shuffled_16_values() }, - { "all_equal", all_equal() }, - { "ascending", ascending() }, - { "descending", descending() }, - { "pipe_organ", pipe_organ() }, - { "push_front", push_front() }, - { "push_middle", push_middle() }, - { "ascending_sawtooth", ascending_sawtooth() }, - { "descending_sawtooth", descending_sawtooth() }, - { "alternating", alternating() }, - { "alternating_16_values", alternating_16_values() } + { "shuffled", dist::shuffled() }, + { "shuffled_16_values", dist::shuffled_16_values() }, + { "all_equal", dist::all_equal() }, + { "ascending", dist::ascending() }, + { "descending", dist::descending() }, + { "pipe_organ", dist::pipe_organ() }, + { "push_front", dist::push_front() }, + { "push_middle", dist::push_middle() }, + { "ascending_sawtooth", dist::ascending_sawtooth() }, + { "descending_sawtooth", dist::descending_sawtooth() }, + { "alternating", dist::alternating() }, + { "alternating_16_values", dist::alternating_16_values() }, }; std::pair sorts[] = { @@ -80,7 +80,7 @@ int main() { "quick_sort", cppsort::quick_sort }, { "spread_sort", cppsort::spread_sort }, { "std_sort", cppsort::std_sort }, - { "verge_sort", cppsort::verge_sort } + { "verge_sort", cppsort::verge_sort }, }; std::size_t sizes[] = { 1'000'000 }; diff --git a/benchmarks/small-array/benchmark.cpp b/benchmarks/small-array/benchmark.cpp index 7227263b..99addbb8 100644 --- a/benchmarks/small-array/benchmark.cpp +++ b/benchmarks/small-array/benchmark.cpp @@ -45,7 +45,7 @@ template< typename Sorter, typename DistributionFunction > -auto time_it(Sorter sorter, DistributionFunction dist) +auto time_it(Sorter sorter, DistributionFunction distribution) -> double { // Seed the distribution manually to ensure that all algorithms @@ -54,13 +54,13 @@ auto time_it(Sorter sorter, DistributionFunction dist) std::vector cycles; - // Generate and sort arrays of size N thanks to dist + // Generate and sort arrays of size N thanks to distribution auto total_start = clock_type::now(); auto total_end = clock_type::now(); while (std::chrono::duration_cast(total_end - total_start) < max_run_time && cycles.size() < max_runs_per_size) { std::array arr; - dist(arr.begin(), N); + distribution(arr.begin(), N); std::uint64_t start = rdtsc(); sorter(arr); std::uint64_t end = rdtsc(); @@ -139,12 +139,12 @@ int main() std::cout << "SEED: " << seed << '\n'; time_distributions(); } diff --git a/tools/test_failing_sorter.cpp b/tools/test_failing_sorter.cpp index 9a47d70a..e484a32b 100644 --- a/tools/test_failing_sorter.cpp +++ b/tools/test_failing_sorter.cpp @@ -9,14 +9,17 @@ #include #include #include +#include #include #include #include #include -#include "../testsuite/distributions.h" +#include "../benchmarks/benchmarking-tools/distributions.h" +#include "../testsuite/testing-tools/algorithm.h" +#include "../testsuite/testing-tools/wrapper.h" struct shuffled_string: - dist::distribution + dist::base_distribution { template auto operator()(OutputIterator out, long long int size, T start=T(0)) const @@ -31,7 +34,7 @@ struct shuffled_string: T end = start + size; for (auto i = start ; i < end ; ++i) { auto s = std::to_string(i); - vec.push_back(std::string(100 - s.size(), '0') + std::move(s)); + vec.push_back(std::string(50 - s.size(), '0') + std::move(s)); } std::shuffle(std::begin(vec), std::end(vec), engine); std::move(std::begin(vec), std::end(vec), out); @@ -44,12 +47,11 @@ void test(const char* name) const int size = 491; std::vector collection; - collection.reserve(size); auto distribution = shuffled_string{}; - distribution(std::back_inserter(collection), size, -125); + distribution(std::back_inserter(collection), size); auto copy = collection; - std::sort(std::begin(copy), std::end(copy)); + cppsort::quick_sort(std::begin(copy), std::end(copy)); std::cout << std::boolalpha << name << '\n'; auto sorter = Sorter{}; @@ -60,7 +62,7 @@ void test(const char* name) std::cout << (collection == copy) << '\n'; std::cout << "were some elements altered? "; auto copy2 = collection; - std::sort(std::begin(collection), std::end(collection)); + cppsort::quick_sort(std::begin(collection), std::end(collection)); std::cout << (collection != copy) << '\n'; std::cout << '\n' @@ -88,5 +90,5 @@ void test(const char* name) int main() { - test("poplar_sort"); + test>("verge_sort"); } From affe28b4dba120172df3ed7e84ce61d4b83a4235 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 21 Dec 2020 23:52:55 +0100 Subject: [PATCH 32/90] Add an explicit stable_adapter specializations An likewise for stable_adapter. Instead of simply relying on make_stable, the new specializations use a variant of vergesort that detects strictly descending runs instead of non-ascending ones, and wraps the fallback sorter in stable_t. The technique of detecting non-descending and strictly descending runs is a trick borrowed from timsort in order to preserve stability in natural mergesort algorithms: descending runs are reversed in-place, so equivalent elements wouldn't retain their original order when reversed in such runs. While verge_adapter does not handle bidirectional iterators, the underlying code was changed so that it will be easier to do in the future, but currently this involves some dirty tricks with a stripped down implementation of a sized iterator. This will make the transition to C++20 ranges and standard sized iterators and sentinels easier in the long term. Meanwhile it is just an implementation detail. This commit addresses external issue Morwenn/vergesort#11 and somehow Morwenn/vergesort#7 too. cpp-sort is a better recipient for those variations on vergesort than the standalone project, but some cross-project documentation will be needed anyway. --- docs/Sorter-adapters.md | 8 + docs/Sorters.md | 4 + include/cpp-sort/adapters/stable_adapter.h | 21 +- include/cpp-sort/adapters/verge_adapter.h | 26 +- include/cpp-sort/detail/quick_merge_sort.h | 13 + include/cpp-sort/detail/sized_iterator.h | 106 +++++++++ include/cpp-sort/detail/vergesort.h | 222 ++++++++++++------ include/cpp-sort/sorters/quick_merge_sorter.h | 5 +- include/cpp-sort/sorters/verge_sorter.h | 33 ++- 9 files changed, 351 insertions(+), 87 deletions(-) create mode 100644 include/cpp-sort/detail/sized_iterator.h diff --git a/docs/Sorter-adapters.md b/docs/Sorter-adapters.md index 59be5b4e..33aee2a8 100644 --- a/docs/Sorter-adapters.md +++ b/docs/Sorter-adapters.md @@ -244,8 +244,10 @@ One can provide a dedicated stable algorithm by explicitly specializing `stable_ * [`default_sorter`][default-sorter] * [`std_sorter`][std-sorter] +* [`verge_sorter`][verge-sorter] * [`hybrid_adapter`][hybrid-adapter] * [`self_sort_adapter`][self-sort-adapter] +* [`verge_adapter`][verge-adapter] If such a user specialization is provided, it shall alias `is_always_stable` to `std::true_type` and provide a `type` member type which follows the rules mentioned earlier. @@ -284,6 +286,10 @@ template struct verge_adapter; ``` +When wrapped into [`stable_adapter`][stable-adapter], it has a slightly different behaviour: it detects strictly descending runs instead of non-ascending ones, and wraps the fallback sorter with `stable_t`. The *resulting sorter* is stable, and faster than just using `make_stable`. + +*New in version 1.9.0:* explicit specialization for `stable_adapter`. + [ctad]: https://en.cppreference.com/w/cpp/language/class_template_argument_deduction [cycle-sort]: https://en.wikipedia.org/wiki/Cycle_sort @@ -302,4 +308,6 @@ struct verge_adapter; [std-sort]: https://en.cppreference.com/w/cpp/algorithm/sort [std-sorter]: https://github.com/Morwenn/cpp-sort/wiki/Sorters#std_sorter [std-stable-sort]: https://en.cppreference.com/w/cpp/algorithm/stable_sort + [verge-adapter]: https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#verge_adapter + [verge-sorter]: https://github.com/Morwenn/cpp-sort/wiki/Sorters#verge_sorter [vergesort-fallbacks]: https://github.com/Morwenn/vergesort/blob/master/fallbacks.md diff --git a/docs/Sorters.md b/docs/Sorters.md index 994b3cfe..9bbd5f9c 100644 --- a/docs/Sorters.md +++ b/docs/Sorters.md @@ -388,8 +388,12 @@ Vergesort's complexity is bound either by its optimization layer or by the fallb * When it doesn't find big runs, the complexity is bound by the fallback sorter: depending on the category of iterators you can refer to the tables of either `pdq_sorter` or `quick_merge_sorter`. * When it does find big runs, vergesort's complexity is bound by the merging phase of its optimization layer. In such a case, `inplace_merge` is used to merge the runs: it will use additional memory if any is available, in which case vergesort is O(n log n). If there isn't much extra memory available, it may still require O(log n) extra memory (and thus raise an `std::bad_alloc` if there isn't that much memory available) in which case the complexity falls to O(n log n log log n). It should not happen that much, and the additional *log log n* factor is likely irrelevant for most real-world applications. +When wrapped into [`stable_adapter`][stable-adapter], it has a slightly different behaviour: it detects strictly descending runs instead of non-ascending ones, and wraps the fallback sorter with `stable_t`. This make the specialization stable, and faster than just using `make_stable`. + *Changed in version 1.6.0:* when sorting a collection made of bidirectional iterators, `verge_sorter` falls back to `quick_merge_sorter` instead of `quick_sorter`. +*New in version 1.9.0:* explicit specialization for `stable_adapter`. + ## Type-specific sorters The following sorters are available but will only work for some specific types instead of using a user-provided comparison function. Some of them also accept projections as long as the result of the projection can be handled by the sorter. diff --git a/include/cpp-sort/adapters/stable_adapter.h b/include/cpp-sort/adapters/stable_adapter.h index 9ff28dcb..a140b002 100644 --- a/include/cpp-sort/adapters/stable_adapter.h +++ b/include/cpp-sort/adapters/stable_adapter.h @@ -24,6 +24,7 @@ #include "../detail/checkers.h" #include "../detail/iterator_traits.h" #include "../detail/memory.h" +#include "../detail/sized_iterator.h" namespace cppsort { @@ -132,6 +133,23 @@ namespace cppsort ); } + template< + typename ForwardIterator, + typename Compare, + typename Projection, + typename Sorter + > + auto make_stable_and_sort(sized_iterator first, difference_type_t size, + Compare&& compare, Projection&& projection, Sorter&& sorter) + -> decltype(auto) + { + // Hack to get the stable bidirectional version of vergesort + // to work correctly without duplicating tons of code + return make_stable_and_sort(first.base(), size, + std::move(compare), std::move(projection), + std::move(sorter)); + } + //////////////////////////////////////////////////////////// // make_stable_impl @@ -175,7 +193,8 @@ namespace cppsort Compare compare={}, Projection projection={}) const -> decltype(auto) { - return make_stable_and_sort(first, std::distance(first, last), + using std::distance; // Hack for sized_iterator + return make_stable_and_sort(first, distance(first, last), std::move(compare), std::move(projection), this->get()); } diff --git a/include/cpp-sort/adapters/verge_adapter.h b/include/cpp-sort/adapters/verge_adapter.h index 3bb8ed8e..229b6f93 100644 --- a/include/cpp-sort/adapters/verge_adapter.h +++ b/include/cpp-sort/adapters/verge_adapter.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -25,7 +26,7 @@ namespace cppsort namespace detail { - template + template struct verge_adapter_impl: utility::adapter_storage { @@ -55,27 +56,38 @@ namespace cppsort "verge_adapter requires at least random-access iterators" ); - verge::sort(std::move(first), std::move(last), last - first, - std::move(compare), std::move(projection), - this->get()); + verge::sort(std::move(first), std::move(last), last - first, + std::move(compare), std::move(projection), + this->get()); } //////////////////////////////////////////////////////////// // Sorter traits using iterator_category = std::random_access_iterator_tag; - using is_always_stable = std::false_type; + using is_always_stable = std::integral_constant; }; } template struct verge_adapter: - sorter_facade> + sorter_facade> { verge_adapter() = default; constexpr explicit verge_adapter(FallbackSorter sorter): - sorter_facade>(std::move(sorter)) + sorter_facade>(std::move(sorter)) + {} + }; + + template + struct stable_adapter>: + sorter_facade> + { + stable_adapter() = default; + + constexpr explicit stable_adapter(verge_adapter sorter): + sorter_facade>(std::move(sorter)) {} }; } diff --git a/include/cpp-sort/detail/quick_merge_sort.h b/include/cpp-sort/detail/quick_merge_sort.h index 9371515d..a223e042 100644 --- a/include/cpp-sort/detail/quick_merge_sort.h +++ b/include/cpp-sort/detail/quick_merge_sort.h @@ -17,6 +17,7 @@ #include "iterator_traits.h" #include "nth_element.h" #include "quicksort.h" +#include "sized_iterator.h" #include "swap_ranges.h" namespace cppsort @@ -149,6 +150,18 @@ namespace detail } small_sort(first, last, size, std::move(compare), std::move(projection)); } + + template + auto quick_merge_sort(sized_iterator first, sized_iterator last, + difference_type_t size, + Compare compare, Projection projection) + -> void + { + // Hack to get the stable bidirectional version of vergesort + // to work correctly without duplicating tons of code + quick_merge_sort(first.base(), last.base(), size, + std::move(compare), std::move(projection)); + } }} #endif // CPPSORT_DETAIL_QUICK_MERGE_SORT_H_ diff --git a/include/cpp-sort/detail/sized_iterator.h b/include/cpp-sort/detail/sized_iterator.h new file mode 100644 index 00000000..d65b1fad --- /dev/null +++ b/include/cpp-sort/detail/sized_iterator.h @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2020 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_DETAIL_SIZED_ITERATOR_H_ +#define CPPSORT_DETAIL_SIZED_ITERATOR_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// + +namespace cppsort +{ +namespace detail +{ + //////////////////////////////////////////////////////////// + // Mostly a hack to avoid some gratuitous performance loss + // by passing bidirectional iterators + size to a function + // accepting a pair of iterators. It is worse than the + // equivalent C++20 features, but should be good enough for + // the internal use we make of it. + // + // NOTE: the full iterator features are not provided, this + // is intentional to avoid unintentional uses of the + // class in the library's internals. + + template + class sized_iterator + { + public: + + //////////////////////////////////////////////////////////// + // Public types + + using iterator_category = iterator_category_t; + using iterator_type = Iterator; + using value_type = value_type_t; + using difference_type = difference_type_t; + using pointer = pointer_t; + using reference = reference_t; + + //////////////////////////////////////////////////////////// + // Constructors + + sized_iterator() = default; + + constexpr sized_iterator(Iterator it, difference_type size): + _it(std::move(it)), + _size(size) + {} + + //////////////////////////////////////////////////////////// + // Members access + + auto base() const + -> iterator_type + { + return _it; + } + + auto size() const + -> difference_type + { + return _size; + } + + //////////////////////////////////////////////////////////// + // Element access + + auto operator*() const + -> reference + { + return *_it; + } + + auto operator->() const + -> pointer + { + return &(operator*()); + } + + private: + + Iterator _it; + difference_type _size; + }; + + // Alternative to std::distance meant to be picked up by ADL in + // specific places, uses the size of the *second* iterator + template + constexpr auto distance(sized_iterator, sized_iterator last) + -> difference_type_t + { + return last.size(); + } + + template + auto make_sized_iterator(Iterator it, difference_type_t size) + -> sized_iterator + { + return { it, size }; + } + +}} + +#endif // CPPSORT_DETAIL_SIZED_ITERATOR_H_ diff --git a/include/cpp-sort/detail/vergesort.h b/include/cpp-sort/detail/vergesort.h index f7f96959..e18b257f 100644 --- a/include/cpp-sort/detail/vergesort.h +++ b/include/cpp-sort/detail/vergesort.h @@ -11,7 +11,9 @@ #include #include #include +#include #include +#include #include #include "bitops.h" #include "config.h" @@ -21,6 +23,7 @@ #include "quick_merge_sort.h" #include "reverse.h" #include "rotate.h" +#include "sized_iterator.h" #include "upper_bound.h" namespace cppsort @@ -92,16 +95,24 @@ namespace verge //////////////////////////////////////////////////////////// // Vergesort main algorithms - template - auto sort_bidirectional(BidirectionalIterator first, BidirectionalIterator last, - difference_type_t size, - Compare compare, Projection projection) + template< + bool Stable, + typename BidirectionalIterator, + typename Compare, + typename Projection, + typename Fallback + > + auto sort(std::bidirectional_iterator_tag, + BidirectionalIterator first, BidirectionalIterator last, + difference_type_t size, + Compare compare, Projection projection, Fallback fallback) -> void { if (size < 128) { // vergesort is inefficient for small collections - quick_merge_sort(std::move(first), std::move(last), size, - std::move(compare), std::move(projection)); + fallback(make_sized_iterator(first, size), + make_sized_iterator(last, size), + std::move(compare), std::move(projection)); return; } @@ -109,7 +120,7 @@ namespace verge auto&& comp = utility::as_function(compare); auto&& proj = utility::as_function(projection); - // Limit under which quick_merge_sort is used + // Limit under which fallback is used int minrun_limit = size / log2(size); // Vergesort detects big runs in ascending or descending order, @@ -135,7 +146,8 @@ namespace verge // Choose whether to look for an ascending or descending // run depending on the value of the first two elements; // when comparing equivalent there is a bias towards - // ascending runs because they don't have to be reversed + // ascending runs because they don't have to be reversed, + // and is always the right thing to do when sorting stably if (comp(proj(*next), proj(*current))) { goto find_descending; } else { @@ -181,7 +193,7 @@ namespace verge ++current; ++next; - // Find an ascending run + // Find a non-descending run difference_type run_size = 2; while (next != last) { if (comp(proj(*next), proj(*current))) break; @@ -192,7 +204,9 @@ namespace verge if (run_size > minrun_limit) { if (begin_unsorted != last) { - quick_merge_sort(begin_unsorted, begin_rng, size_unsorted, compare, projection); + fallback(make_sized_iterator(begin_unsorted, size_unsorted), + make_sized_iterator(begin_rng, size_unsorted), + compare, projection); runs.push_back({ begin_rng, size_unsorted} ); runs.push_back({ next, run_size }); begin_unsorted = last; @@ -215,7 +229,6 @@ namespace verge } else { goto find_ascending; } - } else { size_unsorted += (run_size - 1); if (begin_unsorted == last) { @@ -233,18 +246,30 @@ namespace verge ++current; ++next; - // Find a descending run difference_type run_size = 2; - while (next != last) { - if (comp(proj(*current), proj(*next))) break; - ++current; - ++next; - ++run_size; + if (Stable) { + // Find a strictly descending run + while (next != last) { + if (not comp(proj(*next), proj(*current))) break; + ++current; + ++next; + ++run_size; + } + } else { + // Find a non-ascending run + while (next != last) { + if (comp(proj(*current), proj(*next))) break; + ++current; + ++next; + ++run_size; + } } if (run_size > minrun_limit) { if (begin_unsorted != last) { - quick_merge_sort(begin_unsorted, begin_rng, size_unsorted, compare, projection); + fallback(make_sized_iterator(begin_unsorted, size_unsorted), + make_sized_iterator(begin_rng, size_unsorted), + compare, projection); runs.push_back({ begin_rng, size_unsorted }); detail::reverse(begin_rng, next); runs.push_back({ next, run_size }); @@ -281,18 +306,33 @@ namespace verge } if (begin_unsorted != last) { - quick_merge_sort(begin_unsorted, last, size_unsorted, compare, projection); - runs.push_back({ last, size_unsorted + 1 }); + // When run_size is added to size_unsorted, we retrieve one from + // it because it assumes that the last element is part of the + // next run, so we add one back here to compensate + ++size_unsorted; + if (size_unsorted > 1) { + fallback(make_sized_iterator(begin_unsorted, size_unsorted), + make_sized_iterator(last, size_unsorted), + compare, projection); + } + runs.push_back({ last, size_unsorted }); } // Last step: merge the runs verge::merge_runs(first, runs, std::move(compare), std::move(projection)); } - template - auto sort_random_access(RandomAccessIterator first, RandomAccessIterator last, - difference_type_t size, - Compare compare, Projection projection, Fallback fallback) + template< + bool Stable, + typename RandomAccessIterator, + typename Compare, + typename Projection, + typename Fallback + > + auto sort(std::random_access_iterator_tag, + RandomAccessIterator first, RandomAccessIterator last, + difference_type_t size, + Compare compare, Projection projection, Fallback fallback) -> void { if (size < 128) { @@ -348,21 +388,45 @@ namespace verge auto next2 = next; if (comp(proj(*next), proj(*current))) { - // Found a decreasing run, scan to the left and to the right + // Found a descending run, scan to the left and to the right // until the limits of the run are reached - do { - --current; - --next; - if (comp(proj(*current), proj(*next))) break; - } while (current != begin_range); - if (comp(proj(*current), proj(*next))) ++current; + if (Stable) { + // Find a strictly descending run to avoid breaking + // the stability of the algorithm with reverse() + do { + --current; + --next; + if (not comp(proj(*next), proj(*current))) break; + } while (current != begin_range); + if (not comp(proj(*next), proj(*current))) { + ++current; + } - ++current2; - ++next2; - while (next2 != last) { - if (comp(proj(*current2), proj(*next2))) break; ++current2; ++next2; + while (next2 != last) { + if (not comp(proj(*next2), proj(*current2))) break; + ++current2; + ++next2; + } + } else { + // Find a non-ascending sequence + do { + --current; + --next; + if (comp(proj(*current), proj(*next))) break; + } while (current != begin_range); + if (comp(proj(*current), proj(*next))) { + ++current; + } + + ++current2; + ++next2; + while (next2 != last) { + if (comp(proj(*current2), proj(*next2))) break; + ++current2; + ++next2; + } } // Check whether we found a big enough sorted sequence @@ -384,8 +448,8 @@ namespace verge } } } else { - // Found an increasing run, scan to the left and to the right - // until the limits of the run are reached + // Found an non-descending run, scan to the left and to + // the right until the limits of the run are reached do { --current; --next; @@ -439,56 +503,76 @@ namespace verge //////////////////////////////////////////////////////////// // Vergesort main interface - template - auto sort(std::bidirectional_iterator_tag, - BidirectionalIterator first, BidirectionalIterator last, - difference_type_t size, - Compare compare, Projection projection, Fallback /* fallback */) - -> void + template + auto get_maybe_stable(std::true_type, Sorter&& sorter) + -> cppsort::stable_adapter { - // This overload exists purely for the sake of genericity - // and to make future evolutions easier, it is actually - // never called with anything else than pdq_sorter as a - // fallback for now, which can't even be used by the - // bidirectional algorithm - sort_bidirectional(std::move(first), std::move(last), size, - std::move(compare), std::move(projection)); + return cppsort::stable_adapter(std::move(sorter)); } - template - auto sort(std::random_access_iterator_tag, - RandomAccessIterator first, RandomAccessIterator last, - difference_type_t size, - Compare compare, Projection projection, Fallback fallback) - -> void + template + auto get_maybe_stable(std::false_type, Sorter&& sorter) + -> Sorter { - // Forward every parameter as is - sort_random_access(std::move(first), std::move(last), size, - std::move(compare), std::move(projection), - std::move(fallback)); + return std::move(sorter); } - template + template< + bool Stable, + typename BidirectionalIterator, + typename Compare, + typename Projection, + typename Fallback + > auto sort(BidirectionalIterator first, BidirectionalIterator last, difference_type_t size, Compare compare, Projection projection, Fallback fallback) -> void { - verge::sort(iterator_category_t{}, - std::move(first), std::move(last), size, - std::move(compare), std::move(projection), - std::move(fallback)); + // Adapt the fallback sorter depending on whether a stable + // or an unstable sort is wanted + verge::sort(iterator_category_t{}, + std::move(first), std::move(last), size, + std::move(compare), std::move(projection), + get_maybe_stable(std::integral_constant{}, std::move(fallback))); } - template + constexpr auto default_sorter_for_impl(std::bidirectional_iterator_tag) + -> cppsort::quick_merge_sorter + { + return {}; + } + + constexpr auto default_sorter_for_impl(std::random_access_iterator_tag) + -> cppsort::pdq_sorter + { + return {}; + } + + template + constexpr auto default_sorter_for(Iterator) + -> decltype(auto) + { + iterator_category_t category; + return default_sorter_for_impl(category); + } + + template< + bool Stable, + typename BidirectionalIterator, + typename Compare, + typename Projection + > auto sort(BidirectionalIterator first, BidirectionalIterator last, difference_type_t size, Compare compare, Projection projection) -> void { - verge::sort(std::move(first), std::move(last), size, - std::move(compare), std::move(projection), - cppsort::pdq_sorter{}); + // Pick a default sorter based on the iterator category when + // none is provided + verge::sort(first, last, size, + std::move(compare), std::move(projection), + default_sorter_for(first)); } }}} diff --git a/include/cpp-sort/sorters/quick_merge_sorter.h b/include/cpp-sort/sorters/quick_merge_sorter.h index 4544a494..49aed9dd 100644 --- a/include/cpp-sort/sorters/quick_merge_sorter.h +++ b/include/cpp-sort/sorters/quick_merge_sorter.h @@ -74,8 +74,9 @@ namespace cppsort "quick_merge_sorter requires at least forward iterators" ); - auto dist = std::distance(first, last); - quick_merge_sort(std::move(first), std::move(last), dist, + using std::distance; // Hack for sized_iterator + quick_merge_sort(std::move(first), std::move(last), + distance(first, last), std::move(compare), std::move(projection)); } diff --git a/include/cpp-sort/sorters/verge_sorter.h b/include/cpp-sort/sorters/verge_sorter.h index 1af162cb..7869c1ba 100644 --- a/include/cpp-sort/sorters/verge_sorter.h +++ b/include/cpp-sort/sorters/verge_sorter.h @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include #include #include @@ -23,10 +25,11 @@ namespace cppsort { //////////////////////////////////////////////////////////// - // Sorter + // Sorter implementation namespace detail { + template struct verge_sorter_impl { template< @@ -49,9 +52,9 @@ namespace cppsort "verge_sorter requires at least bidirectional iterators" ); - verge::sort(std::begin(iterable), std::end(iterable), - utility::size(iterable), - std::move(compare), std::move(projection)); + verge::sort(std::begin(iterable), std::end(iterable), + utility::size(iterable), + std::move(compare), std::move(projection)); } template< @@ -75,22 +78,36 @@ namespace cppsort ); auto size = std::distance(first, last); - verge::sort(std::move(first), std::move(last), size, - std::move(compare), std::move(projection)); + verge::sort(std::move(first), std::move(last), size, + std::move(compare), std::move(projection)); } //////////////////////////////////////////////////////////// // Sorter traits using iterator_category = std::bidirectional_iterator_tag; - using is_always_stable = std::false_type; + using is_always_stable = std::integral_constant; }; } + //////////////////////////////////////////////////////////// + // Sorters + struct verge_sorter: - sorter_facade + sorter_facade> {}; + template<> + struct stable_adapter: + sorter_facade> + { + stable_adapter() = default; + + constexpr explicit stable_adapter(verge_sorter): + sorter_facade>() + {} + }; + //////////////////////////////////////////////////////////// // Sort function From 23672d1e40b1d8401a13dd335ebd04f979282edd Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 22 Dec 2020 11:08:01 +0100 Subject: [PATCH 33/90] Renanme Codecov GitHub workflow to code-coverage.yml The previous named caused the file to be found by the Codecov bash uploader, which isn't exactly what we want... [ci skip] --- .github/workflows/{codecov.yml => code-coverage.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{codecov.yml => code-coverage.yml} (100%) diff --git a/.github/workflows/codecov.yml b/.github/workflows/code-coverage.yml similarity index 100% rename from .github/workflows/codecov.yml rename to .github/workflows/code-coverage.yml From 1eea82d3e4179540a52ae690446b9bbb9ab9a4f9 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 22 Dec 2020 15:42:16 +0100 Subject: [PATCH 34/90] Add version info in relevant places in the doc [ci skip] --- docs/Home.md | 2 +- docs/Tooling.md | 4 ++-- tools/release-checklist.md | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/Home.md b/docs/Home.md index c3939dd0..e27acd41 100644 --- a/docs/Home.md +++ b/docs/Home.md @@ -1,4 +1,4 @@ -Welcome to the **cpp-sort** wiki! +Welcome to the **cpp-sort 1.9.0** 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/Tooling.md b/docs/Tooling.md index 58d3c644..9312d2bb 100644 --- a/docs/Tooling.md +++ b/docs/Tooling.md @@ -44,10 +44,10 @@ The project's CMake files do offer some options, but they are mainly used to con conan search cpp-sort --remote=conan-center ``` -And then install any version to your local cache as follows (here with version 1.5.1): +And then install any version to your local cache as follows (here with version 1.9.0): ```sh -conan install cpp-sort/1.5.1 +conan install cpp-sort/1.9.0 ``` 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 use the source available in this GitHub repository. diff --git a/tools/release-checklist.md b/tools/release-checklist.md index f05eaafd..9ee5ae8d 100644 --- a/tools/release-checklist.md +++ b/tools/release-checklist.md @@ -19,6 +19,7 @@ development phase: - [ ] conanfile.py - [ ] README.md - [ ] version.h + - [ ] Home.md in the documentation - [ ] Find a name for the new version. - [ ] Open a merge request, let the CI do its job. - [ ] Merge `develop` into `master`. From 49ffe76cf4957452acb0fd87c91057f5770bd0e7 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 22 Dec 2020 15:48:51 +0100 Subject: [PATCH 35/90] Mention that SplitSorter can't throw std::bad_alloc [ci skip] --- docs/Sorters.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/Sorters.md b/docs/Sorters.md index 9bbd5f9c..e0e61a9a 100644 --- a/docs/Sorters.md +++ b/docs/Sorters.md @@ -313,7 +313,7 @@ Implements a [spinsort](https://www.boost.org/doc/libs/1_72_0/libs/sort/doc/html #include ``` -Implements an in-place *SplitSort* as descirbed in *Splitsort—an adaptive sorting algorithm* by Levcopoulos and Petersson. This library implements the simpler "in-place" version of the algorithm described in the paper. +Implements an in-place *SplitSort* as descirbed in *Splitsort — an adaptive sorting algorithm* by Levcopoulos and Petersson. This library implements the simpler "in-place" version of the algorithm described in the paper. | Best | Average | Worst | Memory | Stable | Iterators | | ----------- | ----------- | ----------- | ----------- | ----------- | ------------- | @@ -324,6 +324,8 @@ SplitSort is a [*Rem*-adaptive](https://github.com/Morwenn/cpp-sort/wiki/Measure * While it uses O(n) extra memory to merge some elements, it can run perfectly fine with O(1) extra memory. * Benchmarks shows that drop-merge sort is better when few elements aren't in place, but SplitSort has a lower overhead on random data while still performing better than most general-purpose sorting algorithms when the data is already somewhat sorted. +This sorter can't throw `std::bad_alloc`. + *New in version 1.4.0* ### `std_sorter` From bd0ca5870c816ace91760e3c36bb20aadbd457d3 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 22 Dec 2020 16:43:33 +0100 Subject: [PATCH 36/90] Fix stable_adapter(verge_adapter(...)) constructor Also add a test, which doesn't actually test that the failing constructor works, but will do in the 2.0.0-develop branch. --- include/cpp-sort/adapters/verge_adapter.h | 2 +- testsuite/adapters/mixed_adapters.cpp | 27 ++++++++++++++++++----- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/include/cpp-sort/adapters/verge_adapter.h b/include/cpp-sort/adapters/verge_adapter.h index 229b6f93..3fe6871d 100644 --- a/include/cpp-sort/adapters/verge_adapter.h +++ b/include/cpp-sort/adapters/verge_adapter.h @@ -87,7 +87,7 @@ namespace cppsort stable_adapter() = default; constexpr explicit stable_adapter(verge_adapter sorter): - sorter_facade>(std::move(sorter)) + sorter_facade>(std::move(sorter).get()) {} }; } diff --git a/testsuite/adapters/mixed_adapters.cpp b/testsuite/adapters/mixed_adapters.cpp index 4137b102..c84e097f 100644 --- a/testsuite/adapters/mixed_adapters.cpp +++ b/testsuite/adapters/mixed_adapters.cpp @@ -10,14 +10,10 @@ #include #include #include -#include -#include -#include -#include -#include -#include +#include #include #include +#include #include #include #include @@ -208,3 +204,22 @@ TEST_CASE( "stability of counting_adapter over self_sort_adapter", CHECK( cppsort::is_stable::iterator, std::vector::iterator, std::negate<>)>::value ); } } + +TEST_CASE( "stable_adapter over verge_adapter", + "[stable_adapter][verge_adapter]" ) +{ + using wrapper = generic_stable_wrapper; + std::vector collection; + auto distribution = dist::descending_plateau{}; + distribution(std::back_inserter(collection), 400); + helpers::iota(collection.begin(), collection.end(), 0, &wrapper::order); + + cppsort::stable_adapter< + cppsort::verge_adapter< + cppsort::grail_sorter<> + > + > sorter; + + sorter(collection, &wrapper::value); + CHECK( helpers::is_sorted(collection.begin(), collection.end()) ); +} From 5278270bc1c0567957bd4e982e6c245ce7aa95f0 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 23 Dec 2020 15:17:56 +0100 Subject: [PATCH 37/90] Add a median_of_3_killer distribution to the test suite This specific distribution is implement after the paper A Killer Adversary for Quicksort, by M. D. McIlroy. The goal is to force some pivot-based algorithms to use their fallback algorithm that ensure that they won't turn quadratic. --- testsuite/CMakeLists.txt | 1 + .../distributions/median_of_3_killer.cpp | 63 +++++++++++++++++++ testsuite/testing-tools/distributions.h | 25 ++++++++ 3 files changed, 89 insertions(+) create mode 100644 testsuite/distributions/median_of_3_killer.cpp diff --git a/testsuite/CMakeLists.txt b/testsuite/CMakeLists.txt index 7a900db3..c73b36ad 100644 --- a/testsuite/CMakeLists.txt +++ b/testsuite/CMakeLists.txt @@ -147,6 +147,7 @@ add_executable(main-tests distributions/ascending_sawtooth.cpp distributions/descending.cpp distributions/descending_sawtooth.cpp + distributions/median_of_3_killer.cpp distributions/pipe_organ.cpp distributions/push_front.cpp distributions/push_middle.cpp diff --git a/testsuite/distributions/median_of_3_killer.cpp b/testsuite/distributions/median_of_3_killer.cpp new file mode 100644 index 00000000..2380ad9d --- /dev/null +++ b/testsuite/distributions/median_of_3_killer.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2020 Morwenn + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include +#include + +TEMPLATE_TEST_CASE( "test random-access sorters with median_of_3_killer distribution", "[distributions]", + cppsort::block_sorter<>, + cppsort::block_sorter< + cppsort::utility::dynamic_buffer + >, + cppsort::drop_merge_sorter, + cppsort::grail_sorter<>, + cppsort::grail_sorter< + cppsort::utility::dynamic_buffer + >, + cppsort::heap_sorter, + cppsort::merge_sorter, + cppsort::pdq_sorter, + cppsort::poplar_sorter, + cppsort::quick_merge_sorter, + cppsort::quick_sorter, + cppsort::ska_sorter, + cppsort::smooth_sorter, + cppsort::spin_sorter, + cppsort::split_sorter, + cppsort::spread_sorter, + cppsort::std_sorter, + cppsort::tim_sorter, + cppsort::verge_sorter ) +{ + std::vector collection; + collection.reserve(1000); + auto distribution = dist::median_of_3_killer{}; + distribution(std::back_inserter(collection), 1000); + + TestType sorter; + sorter(collection); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); +} + +TEMPLATE_TEST_CASE( "test bidirectional sorters with median_of_3_killer distribution", "[distributions]", + cppsort::drop_merge_sorter, + cppsort::merge_sorter, + cppsort::quick_merge_sorter, + cppsort::quick_sorter, + cppsort::verge_sorter ) +{ + std::list collection; + auto distribution = dist::median_of_3_killer{}; + distribution(std::back_inserter(collection), 1000); + + TestType sorter; + sorter(collection); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); +} diff --git a/testsuite/testing-tools/distributions.h b/testsuite/testing-tools/distributions.h index c9c35e3e..c2ecca36 100644 --- a/testsuite/testing-tools/distributions.h +++ b/testsuite/testing-tools/distributions.h @@ -241,6 +241,31 @@ namespace dist } } }; + + struct median_of_3_killer: + distribution + { + // This distribution comes from *A Killer Adversary for Quicksort* + // by M. D. McIlroy, and is supposed to trick several quicksort + // implementations with common pivot selection methods go quadratic + + template + auto operator()(OutputIterator out, std::size_t size) const + -> void + { + std::size_t j = size / 2; + for (std::size_t i = 1 ; i < j + 1 ; ++i) { + if (i % 2 != 0) { + *out++ = i; + } else { + *out++ = j + i - 1; + } + } + for (std::size_t i = 1 ; i < j + 1 ; ++i) { + *out++ = 2 * i; + } + } + }; } #endif // CPPSORT_TESTSUITE_DISTRIBUTIONS_H_ From eb5874d29ab62030e73a7906e3ca74d779b74923 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 24 Dec 2020 18:17:40 +0100 Subject: [PATCH 38/90] Tweak merge_insertion_sorter documentation [ci skip] --- docs/Sorters.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Sorters.md b/docs/Sorters.md index e0e61a9a..85df3309 100644 --- a/docs/Sorters.md +++ b/docs/Sorters.md @@ -153,13 +153,13 @@ None of the container-aware algorithms invalidates iterators. #include ``` -Implements the Ford-Johnson merge-insertion sort. I am no researcher and I couldn't find the algorithm's complexity on the internet, so you will have nice question marks instead. This algorithm isn't meant to actually be used and is mostly interesting from a computer science point of view: for really small collections, it has an optimal worst case for the number of comparisons performed; it has been proven that for some sizes, no algorithm can perform fewer comparisons. That said, the algorithm has a rather big memory overhead and performs many move operations; it is really too slow for any real world use. +Implements the Ford-Johnson merge-insertion sort. This algorithm isn't meant to actually be used and is mostly interesting from a computer science point of view: for really small collections, it has an optimal worst case for the number of comparisons performed. It has indeed been proved that for some sizes, no algorithm can perform fewer comparisons. That said, the algorithm has a rather big memory overhead and performs many move operations; it is really too slow for any real world use. | Best | Average | Worst | Memory | Stable | Iterators | | ----------- | ----------- | ----------- | ----------- | ----------- | ------------- | | ? | n log n | n log n | n | No | Random-access | -*Until version 1.7.0:* it is worth noting that the algorithm uses GNU's [`bitmap_allocator`](https://gcc.gnu.org/onlinedocs/libstdc++/manual/bitmap_allocator.html) when possible at some places instead of the default allocator. I noticed speed improvements up to 25%, but that still doesn't make the algorithm anywhere near fast. +*Until version 1.7.0:* the algorithm used GNU's [`bitmap_allocator`](https://gcc.gnu.org/onlinedocs/libstdc++/manual/bitmap_allocator.html) with `std::list` when possible instead of the default allocator, leading to speed improvements up to 25%. *Changed in version 1.7.0:* the `std::list` used by the algorithm has been replaced with a custom list implementation whose speed does not depend on the availability of some allocator, and which should be faster than the previous implementation in any case (still not anywhere near fast). From d38cb80ae0ba493ad7a37180a184332e4c47b05c Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 2 Jan 2021 16:34:50 +0100 Subject: [PATCH 39/90] Add a GitHub Action workflow for MacOS builds (#176) This commit also comments the Travis CI MacOS configurations, because they there too slow to run to completion anyway. [ci skip] --- .github/workflows/build-macos.yml | 56 +++++++++++++++++++++++++++++++ .travis.yml | 22 ++++++------ 2 files changed, 67 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/build-macos.yml diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml new file mode 100644 index 00000000..ab01cefd --- /dev/null +++ b/.github/workflows/build-macos.yml @@ -0,0 +1,56 @@ +# Copyright (c) 2020 Morwenn +# SPDX-License-Identifier: MIT + +name: MacOS Builds + +on: + push: + paths: + - 'cmake/**' + - 'examples/**' + - 'include/**' + - 'testsuite/**' + pull_request: + paths: + - 'cmake/**' + - 'examples/**' + - 'include/**' + - 'testsuite/**' + +jobs: + build: + runs-on: macos-10.15 + strategy: + matrix: + cxx: [clang++-11, g++-9] + build_type: [Debug, Release] + generator: Ninja + include: + - sanitize: address + - sanitize: undefined + + steps: + - uses: actions/checkout@v2 + + - name: Configure CMake + working-directory: ${{runner.workspace}} + env: + CXX: ${{matrix.cxx}} + run: > + cmake -H${{github.event.repository.name}} -Bbuild + -DCMAKE_CONFIGURATION_TYPES=${{matrix.build_type}} + -DCMAKE_BUILD_TYPE=${{matrix.build_type}} + -DSANITIZE=${{matrix.sanitize}} + -G"${{matrix.generator}}" + -DBUILD_EXAMPLES=ON + + - name: Build the test suite + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake --build . --config ${{matrix.build_type}} -j 2 + + - name: Run the test suite + env: + CTEST_OUTPUT_ON_FAILURE: 1 + working-directory: ${{runner.workspace}}/build + run: ctest -C ${{matrix.build_type}} -j 2 diff --git a/.travis.yml b/.travis.yml index 7d7428c7..6ebed5dc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -123,17 +123,17 @@ matrix: - valgrind compiler: clang - - os: osx - osx_image: xcode9.4 - env: BUILD_TYPE=Release CMAKE_GENERATOR="Ninja" - addons: - homebrew: - update: true - packages: - - ccache - - cmake - - ninja - compiler: clang + # - os: osx + # osx_image: xcode9.4 + # env: BUILD_TYPE=Release CMAKE_GENERATOR="Ninja" + # addons: + # homebrew: + # update: true + # packages: + # - ccache + # - cmake + # - ninja + # compiler: clang # Windows GCC - os: windows From cca02f7c313be275698268e8b1b0612b87adb36e Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 2 Jan 2021 16:39:37 +0100 Subject: [PATCH 40/90] Fix potential move-after-free bug in pdqsort --- include/cpp-sort/detail/pdqsort.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/include/cpp-sort/detail/pdqsort.h b/include/cpp-sort/detail/pdqsort.h index 93cbb2c3..fec53531 100644 --- a/include/cpp-sort/detail/pdqsort.h +++ b/include/cpp-sort/detail/pdqsort.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2020 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ @@ -557,11 +557,13 @@ namespace detail utility::is_probably_branchless_comparison_v && utility::is_probably_branchless_projection_v; - if ((end - begin) < 2) return; + auto size = end - begin; + if (size < 2) return; + pdqsort_detail::pdqsort_loop( std::move(begin), std::move(end), std::move(compare), std::move(projection), - detail::log2(end - begin)); + detail::log2(size)); } }} From 6d2976ee8626d06379f441163b91067b1a6ff0ad Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 2 Jan 2021 16:58:48 +0100 Subject: [PATCH 41/90] MacOS builds: more work [ci skip] --- .github/workflows/build-macos.yml | 49 ++++++++++++++++--------------- .travis.yml | 24 +++++++-------- 2 files changed, 38 insertions(+), 35 deletions(-) diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index ab01cefd..7ddb49c9 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -6,51 +6,54 @@ name: MacOS Builds on: push: paths: + - 'CMakeLists.txt' - 'cmake/**' - 'examples/**' - 'include/**' - 'testsuite/**' pull_request: paths: + - 'CMakeLists.txt' - 'cmake/**' - 'examples/**' - 'include/**' - 'testsuite/**' - + jobs: build: runs-on: macos-10.15 + strategy: matrix: cxx: [clang++-11, g++-9] build_type: [Debug, Release] - generator: Ninja + generator: [Ninja] include: - sanitize: address - sanitize: undefined steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v2 - - name: Configure CMake - working-directory: ${{runner.workspace}} - env: - CXX: ${{matrix.cxx}} - run: > - cmake -H${{github.event.repository.name}} -Bbuild - -DCMAKE_CONFIGURATION_TYPES=${{matrix.build_type}} - -DCMAKE_BUILD_TYPE=${{matrix.build_type}} - -DSANITIZE=${{matrix.sanitize}} - -G"${{matrix.generator}}" - -DBUILD_EXAMPLES=ON + - name: Configure CMake + working-directory: ${{runner.workspace}} + env: + CXX: ${{matrix.cxx}} + run: > + cmake -H${{github.event.repository.name}} -Bbuild + -DCMAKE_CONFIGURATION_TYPES=${{matrix.build_type}} + -DCMAKE_BUILD_TYPE=${{matrix.build_type}} + -DSANITIZE=${{matrix.sanitize}} + -G"${{matrix.generator}}" + -DBUILD_EXAMPLES=ON - - name: Build the test suite - shell: bash - working-directory: ${{runner.workspace}}/build - run: cmake --build . --config ${{matrix.build_type}} -j 2 + - name: Build the test suite + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake --build . --config ${{matrix.build_type}} -j 2 - - name: Run the test suite - env: - CTEST_OUTPUT_ON_FAILURE: 1 - working-directory: ${{runner.workspace}}/build - run: ctest -C ${{matrix.build_type}} -j 2 + - name: Run the test suite + env: + CTEST_OUTPUT_ON_FAILURE: 1 + working-directory: ${{runner.workspace}}/build + run: ctest -C ${{matrix.build_type}} -j 2 diff --git a/.travis.yml b/.travis.yml index 6ebed5dc..426f733a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,18 +110,18 @@ matrix: compiler: gcc # OSX clang - - os: osx - osx_image: xcode9.4 - env: BUILD_TYPE=Debug USE_VALGRIND=true CMAKE_GENERATOR="Ninja" - addons: - homebrew: - update: true - packages: - - ccache - - cmake - - ninja - - valgrind - compiler: clang + # - os: osx + # osx_image: xcode9.4 + # env: BUILD_TYPE=Debug USE_VALGRIND=true CMAKE_GENERATOR="Ninja" + # addons: + # homebrew: + # update: true + # packages: + # - ccache + # - cmake + # - ninja + # - valgrind + # compiler: clang # - os: osx # osx_image: xcode9.4 From a4de2d0584fda86b72d5d81dcdce682369ca2e48 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 2 Jan 2021 17:05:38 +0100 Subject: [PATCH 42/90] Additional files can trigger the Codecov action when changed [ci skip] --- .github/workflows/code-coverage.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index ce5fbddd..4ca0160f 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -10,6 +10,9 @@ on: - develop - 2.0.0-develop paths: + - 'CMakeLists.txt' + - 'cmake/**' + - 'codecov.yml' - 'include/**' - 'testsuite/**' From 4e69cdd8c6ab87fa754bcfdc228a6354fbb592ff Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 2 Jan 2021 17:38:37 +0100 Subject: [PATCH 43/90] Bump downloaded Catch2 version to v2.13.4 --- testsuite/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testsuite/CMakeLists.txt b/testsuite/CMakeLists.txt index c73b36ad..e13fdc78 100644 --- a/testsuite/CMakeLists.txt +++ b/testsuite/CMakeLists.txt @@ -19,7 +19,7 @@ else() message(STATUS "Catch2 not found") download_project(PROJ Catch2 GIT_REPOSITORY https://github.com/catchorg/Catch2 - GIT_TAG v2.13.2 + GIT_TAG v2.13.4 UPDATE_DISCONNECTED 1 ) add_subdirectory(${Catch2_SOURCE_DIR} ${Catch2_BINARY_DIR}) @@ -191,8 +191,8 @@ add_executable(main-tests utility/as_projection.cpp utility/as_projection_iterable.cpp utility/branchless_traits.cpp - utility/chainable_projections.cpp utility/buffer.cpp + utility/chainable_projections.cpp utility/iter_swap.cpp ) configure_tests(main-tests) From 8446c709b486f2e91a17650b0e28d71c4fbd0f83 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 2 Jan 2021 17:48:14 +0100 Subject: [PATCH 44/90] Update MacOS build action to download Ninja [ci skip] --- .github/workflows/build-macos.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 7ddb49c9..81dfeb67 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -34,6 +34,7 @@ jobs: steps: - uses: actions/checkout@v2 + - uses: seanmiddleditch/gha-setup-ninja@master - name: Configure CMake working-directory: ${{runner.workspace}} From acd4bb6f1629f191f2a6109fc65a073773d7c8e8 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 2 Jan 2021 18:41:41 +0100 Subject: [PATCH 45/90] Add missing item to the release checklist [ci skip] --- tools/release-checklist.md | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/release-checklist.md b/tools/release-checklist.md index 9ee5ae8d..baa37767 100644 --- a/tools/release-checklist.md +++ b/tools/release-checklist.md @@ -20,6 +20,7 @@ development phase: - [ ] README.md - [ ] version.h - [ ] Home.md in the documentation + - [ ] Tooling.md/Conan in the documentation - [ ] Find a name for the new version. - [ ] Open a merge request, let the CI do its job. - [ ] Merge `develop` into `master`. From 8f3f5d30a815a61d72aa2ab7fa80857b56278a6e Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 2 Jan 2021 18:54:34 +0100 Subject: [PATCH 46/90] Improve tests for branchless traits (#177) --- testsuite/utility/branchless_traits.cpp | 76 +++++++++++++++++-------- 1 file changed, 52 insertions(+), 24 deletions(-) diff --git a/testsuite/utility/branchless_traits.cpp b/testsuite/utility/branchless_traits.cpp index 68c96ed4..00c72075 100644 --- a/testsuite/utility/branchless_traits.cpp +++ b/testsuite/utility/branchless_traits.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2018 Morwenn + * Copyright (c) 2017-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -18,16 +18,33 @@ TEST_CASE( "test that some specific comparisons are branchless", SECTION( "standard library function objects" ) { - CHECK(( is_probably_branchless_comparison, int>::value )); - CHECK(( is_probably_branchless_comparison, int>::value )); - CHECK(( is_probably_branchless_comparison, long double>::value )); + CHECK(( is_probably_branchless_comparison_v, int> )); + CHECK(( is_probably_branchless_comparison_v, int> )); + CHECK(( is_probably_branchless_comparison_v, long double> )); +#ifdef __cpp_lib_ranges + CHECK(( is_probably_branchless_comparison_v )); + CHECK(( is_probably_branchless_comparison_v )); +#endif - CHECK(( is_probably_branchless_comparison, int>::value )); - CHECK(( is_probably_branchless_comparison, int>::value )); - CHECK(( is_probably_branchless_comparison, long double>::value )); + CHECK(( is_probably_branchless_comparison_v, int> )); + CHECK(( is_probably_branchless_comparison_v, int> )); + CHECK(( is_probably_branchless_comparison_v, long double> )); +#ifdef __cpp_lib_ranges + CHECK(( is_probably_branchless_comparison_v )); + CHECK(( is_probably_branchless_comparison_v )); +#endif - CHECK_FALSE(( is_probably_branchless_comparison, std::string>::value )); - CHECK_FALSE(( is_probably_branchless_comparison, std::string>::value )); + CHECK_FALSE(( is_probably_branchless_comparison_v, std::string> )); + CHECK_FALSE(( is_probably_branchless_comparison_v, std::string> )); +#ifdef __cpp_lib_ranges + CHECK_FALSE(( is_probably_branchless_comparison_v )); +#endif + + CHECK_FALSE(( is_probably_branchless_comparison_v, std::string> )); + CHECK_FALSE(( is_probably_branchless_comparison_v, std::string> )); +#ifdef __cpp_lib_ranges + CHECK_FALSE(( is_probably_branchless_comparison_v )); +#endif } SECTION( "partial/weak/less function objects" ) @@ -36,24 +53,27 @@ TEST_CASE( "test that some specific comparisons are branchless", using weak_t = decltype(cppsort::weak_less); using total_t = decltype(cppsort::total_less); - CHECK(( is_probably_branchless_comparison::value )); - CHECK(( is_probably_branchless_comparison::value )); - CHECK(( is_probably_branchless_comparison::value )); + CHECK(( is_probably_branchless_comparison_v )); + CHECK(( is_probably_branchless_comparison_v )); + CHECK(( is_probably_branchless_comparison_v )); - CHECK(( is_probably_branchless_comparison::value )); - CHECK_FALSE(( is_probably_branchless_comparison::value )); - CHECK_FALSE(( is_probably_branchless_comparison::value )); + CHECK(( is_probably_branchless_comparison_v )); + CHECK_FALSE(( is_probably_branchless_comparison_v )); + CHECK_FALSE(( is_probably_branchless_comparison_v )); - CHECK_FALSE(( is_probably_branchless_comparison::value )); - CHECK_FALSE(( is_probably_branchless_comparison::value )); - CHECK_FALSE(( is_probably_branchless_comparison::value )); + CHECK_FALSE(( is_probably_branchless_comparison_v )); + CHECK_FALSE(( is_probably_branchless_comparison_v )); + CHECK_FALSE(( is_probably_branchless_comparison_v )); } SECTION( "cv-qualified and reference-qualified types" ) { - CHECK(( is_probably_branchless_comparison, const int>::value )); - CHECK(( is_probably_branchless_comparison&, int>::value )); - CHECK(( is_probably_branchless_comparison, int&&>::value )); + CHECK(( is_probably_branchless_comparison_v, const int> )); + CHECK(( is_probably_branchless_comparison_v&, int> )); + CHECK(( is_probably_branchless_comparison_v, int&&> )); +#ifdef __cpp_lib_ranges + CHECK(( is_probably_branchless_comparison_v )); +#endif } } @@ -68,8 +88,16 @@ TEST_CASE( "test that some specific projections are branchless", int bar() { return 0; } }; - CHECK(( is_probably_branchless_projection::value )); + CHECK(( is_probably_branchless_projection_v )); +#if CPPSORT_STD_IDENTITY_AVAILABLE + CHECK(( is_probably_branchless_projection_v )); +#endif + + CHECK(( is_probably_branchless_projection_v )); + CHECK_FALSE(( is_probably_branchless_projection_v )); - CHECK(( is_probably_branchless_projection::value )); - CHECK_FALSE(( is_probably_branchless_projection::value )); +#if defined(__GLIBCXX__) || defined(_LIBCPP_VERSION) + CHECK(( is_probably_branchless_projection_v )); +#endif + CHECK_FALSE(( is_probably_branchless_projection_v )); } From 40ebd2ef3e86fc30b7d0f85a5e36d487d6f3a659 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 2 Jan 2021 19:00:20 +0100 Subject: [PATCH 47/90] Try to fix MacOS builds [ci skip] --- .github/workflows/build-macos.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 81dfeb67..82bad3f0 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -25,7 +25,9 @@ jobs: strategy: matrix: - cxx: [clang++-11, g++-9] + cxx: + - g++-9 + - $(brew --prefix llvm)/bin/clang # Clang 11 build_type: [Debug, Release] generator: [Ninja] include: From 9e25366569e352022b437d73dfd37c9dbc1d10cc Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 3 Jan 2021 13:50:36 +0100 Subject: [PATCH 48/90] Tweak the test suite to make clang-tidy happy --- testsuite/adapters/every_adapter_stateful_sorter.cpp | 8 ++++---- testsuite/adapters/schwartz_adapter_every_sorter.cpp | 4 ++-- .../adapters/schwartz_adapter_every_sorter_reversed.cpp | 4 ++-- testsuite/comparators/case_insensitive_less.cpp | 2 +- testsuite/sorters/ska_sorter_projection.cpp | 4 ++-- testsuite/sorters/spread_sorter_projection.cpp | 6 +++--- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/testsuite/adapters/every_adapter_stateful_sorter.cpp b/testsuite/adapters/every_adapter_stateful_sorter.cpp index 26dca270..8ff2dd8d 100644 --- a/testsuite/adapters/every_adapter_stateful_sorter.cpp +++ b/testsuite/adapters/every_adapter_stateful_sorter.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Morwenn + * Copyright (c) 2019-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -42,11 +42,11 @@ namespace cppsort::quick_merge_sort(std::move(first), std::move(last), std::move(compare)); if (std::is_same::value) { return 1; - } else if (std::is_same::value) { + } + if (std::is_same::value) { return 2; - } else { - return 3; } + return 3; } using iterator_category = IteratorCategory; diff --git a/testsuite/adapters/schwartz_adapter_every_sorter.cpp b/testsuite/adapters/schwartz_adapter_every_sorter.cpp index e7d65245..838e2fe7 100644 --- a/testsuite/adapters/schwartz_adapter_every_sorter.cpp +++ b/testsuite/adapters/schwartz_adapter_every_sorter.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2020 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -100,7 +100,7 @@ TEST_CASE( "type-specific sorters with Schwartzian transform adapter", "[schwart std::vector> collection3; for (int i = -125 ; i < 287 ; ++i) { - collection3.push_back({std::to_string(i)}); + collection3.emplace_back(std::to_string(i)); } std::mt19937 engine(Catch::rngSeed()); std::shuffle(collection3.begin(), collection3.end(), engine); diff --git a/testsuite/adapters/schwartz_adapter_every_sorter_reversed.cpp b/testsuite/adapters/schwartz_adapter_every_sorter_reversed.cpp index d89617c8..63bf43ad 100644 --- a/testsuite/adapters/schwartz_adapter_every_sorter_reversed.cpp +++ b/testsuite/adapters/schwartz_adapter_every_sorter_reversed.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2020 Morwenn + * Copyright (c) 2017-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -66,7 +66,7 @@ TEST_CASE( "type-specific sorters with Schwartzian transform adapter and reverse std::vector> collection3; for (int i = -125 ; i < 287 ; ++i) { - collection3.push_back({std::to_string(i)}); + collection3.emplace_back(std::to_string(i)); } std::mt19937 engine(Catch::rngSeed()); std::shuffle(collection3.begin(), collection3.end(), engine); diff --git a/testsuite/comparators/case_insensitive_less.cpp b/testsuite/comparators/case_insensitive_less.cpp index e480d7c9..b015683a 100644 --- a/testsuite/comparators/case_insensitive_less.cpp +++ b/testsuite/comparators/case_insensitive_less.cpp @@ -26,7 +26,7 @@ namespace sub return res_t::without_locale; } - inline auto case_insensitive_less(const foo&, const foo&, const std::locale) + inline auto case_insensitive_less(const foo&, const foo&, const std::locale&) -> res_t { return res_t::with_locale; diff --git a/testsuite/sorters/ska_sorter_projection.cpp b/testsuite/sorters/ska_sorter_projection.cpp index ecf30c89..00f14265 100644 --- a/testsuite/sorters/ska_sorter_projection.cpp +++ b/testsuite/sorters/ska_sorter_projection.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2020 Morwenn + * Copyright (c) 2017-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -73,7 +73,7 @@ TEST_CASE( "ska_sorter tests with projections", std::vector vec; for (int i = 0 ; i < 100'000 ; ++i) { - vec.push_back({std::to_string(i)}); + vec.emplace_back(std::to_string(i)); } std::shuffle(std::begin(vec), std::end(vec), engine); cppsort::ska_sort(vec, &wrapper::value); diff --git a/testsuite/sorters/spread_sorter_projection.cpp b/testsuite/sorters/spread_sorter_projection.cpp index 29db4bdb..5b181cdb 100644 --- a/testsuite/sorters/spread_sorter_projection.cpp +++ b/testsuite/sorters/spread_sorter_projection.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2020 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -73,7 +73,7 @@ TEST_CASE( "spread_sorter tests with projections", std::vector vec; for (int i = 0 ; i < 100'000 ; ++i) { - vec.push_back({std::to_string(i)}); + vec.emplace_back(std::to_string(i)); } std::shuffle(std::begin(vec), std::end(vec), engine); cppsort::spread_sort(vec, &wrapper::value); @@ -87,7 +87,7 @@ TEST_CASE( "spread_sorter tests with projections", std::vector vec; for (int i = 0 ; i < 100'000 ; ++i) { - vec.push_back({std::to_string(i)}); + vec.emplace_back(std::to_string(i)); } std::shuffle(std::begin(vec), std::end(vec), engine); cppsort::spread_sort(vec, std::greater<>{}, &wrapper::value); From 8a34ca5ce37daf2d03f7d2fefd1c24c98f4580e5 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 3 Jan 2021 14:07:21 +0100 Subject: [PATCH 49/90] Yet another attempt to fix MacOS builds [ci skip] --- .github/workflows/build-macos.yml | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 82bad3f0..5d0e7696 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -40,15 +40,14 @@ jobs: - name: Configure CMake working-directory: ${{runner.workspace}} - env: - CXX: ${{matrix.cxx}} - run: > - cmake -H${{github.event.repository.name}} -Bbuild - -DCMAKE_CONFIGURATION_TYPES=${{matrix.build_type}} - -DCMAKE_BUILD_TYPE=${{matrix.build_type}} - -DSANITIZE=${{matrix.sanitize}} - -G"${{matrix.generator}}" - -DBUILD_EXAMPLES=ON + run: | + export CXX=${{matrix.cxx}} + cmake -H${{github.event.repository.name}} -Bbuild \ + -DCMAKE_CONFIGURATION_TYPES=${{matrix.build_type}} \ + -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \ + -DSANITIZE=${{matrix.sanitize}} \ + -G"${{matrix.generator}}" \ + -DBUILD_EXAMPLES=ON - name: Build the test suite shell: bash From f45a90a9741752d62f8e576880bcf69c9bf4ff0a Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 3 Jan 2021 19:19:00 +0100 Subject: [PATCH 50/90] Fix MacOS builds, disable fail-fast behaviour [ci skip] --- .github/workflows/build-macos.yml | 3 ++- .travis.yml | 12 ------------ 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 5d0e7696..d5ccc05a 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -24,10 +24,11 @@ jobs: runs-on: macos-10.15 strategy: + fail-fast: false matrix: cxx: - g++-9 - - $(brew --prefix llvm)/bin/clang # Clang 11 + - $(brew --prefix llvm)/bin/clang++ # Clang 11 build_type: [Debug, Release] generator: [Ninja] include: diff --git a/.travis.yml b/.travis.yml index 426f733a..d2a58e68 100644 --- a/.travis.yml +++ b/.travis.yml @@ -123,18 +123,6 @@ matrix: # - valgrind # compiler: clang - # - os: osx - # osx_image: xcode9.4 - # env: BUILD_TYPE=Release CMAKE_GENERATOR="Ninja" - # addons: - # homebrew: - # update: true - # packages: - # - ccache - # - cmake - # - ninja - # compiler: clang - # Windows GCC - os: windows language: sh From f28431e71941c178fffdb2eb5ee165a5a1816b45 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 3 Jan 2021 21:10:27 +0100 Subject: [PATCH 51/90] Fix MacOS builds in GitHub Actions once more (#176) This commit is hopefully the last to get things to work with MacOS builds. Compared to the old Travis CI builds, we don't have Valgrind builds, but we have g++, ubsan and asan ones, which is more coverage than what we used to have. [ci skip] --- .github/workflows/build-macos.yml | 29 +++++++++++++++++++++++------ .travis.yml | 14 -------------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index d5ccc05a..6db638af 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2020 Morwenn +# Copyright (c) 2020-2021 Morwenn # SPDX-License-Identifier: MIT name: MacOS Builds @@ -26,14 +26,31 @@ jobs: strategy: fail-fast: false matrix: + # Default configuration corresponds to a release + # build without additional tooling, also provides + # default options for other builds cxx: - g++-9 - $(brew --prefix llvm)/bin/clang++ # Clang 11 - build_type: [Debug, Release] - generator: [Ninja] + build_type: [Release] + sanitize: [''] + + # Below are debug builds with tooling include: - - sanitize: address - - sanitize: undefined + # GCC builds + - cxx: g++-9 + build_type: Debug + sanitize: address + - cxx: g++-9 + build_type: Debug + sanitize: undefined + # Clang builds + - cxx: $(brew --prefix llvm)/bin/clang++ + build_type: Debug + sanitize: address + - cxx: $(brew --prefix llvm)/bin/clang++ + build_type: Debug + sanitize: undefined steps: - uses: actions/checkout@v2 @@ -47,7 +64,7 @@ jobs: -DCMAKE_CONFIGURATION_TYPES=${{matrix.build_type}} \ -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \ -DSANITIZE=${{matrix.sanitize}} \ - -G"${{matrix.generator}}" \ + -GNinja \ -DBUILD_EXAMPLES=ON - name: Build the test suite diff --git a/.travis.yml b/.travis.yml index d2a58e68..8e015e7a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -109,20 +109,6 @@ matrix: - *gcc compiler: gcc - # OSX clang - # - os: osx - # osx_image: xcode9.4 - # env: BUILD_TYPE=Debug USE_VALGRIND=true CMAKE_GENERATOR="Ninja" - # addons: - # homebrew: - # update: true - # packages: - # - ccache - # - cmake - # - ninja - # - valgrind - # compiler: clang - # Windows GCC - os: windows language: sh From 81bde27c656885158e9007c1c9c2453e05525d75 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 4 Jan 2021 14:29:57 +0100 Subject: [PATCH 52/90] Run tests sequentially for better diagnostics [ci skip] --- .github/workflows/build-macos.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 6db638af..04a62544 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -66,7 +66,7 @@ jobs: -DSANITIZE=${{matrix.sanitize}} \ -GNinja \ -DBUILD_EXAMPLES=ON - + - name: Build the test suite shell: bash working-directory: ${{runner.workspace}}/build @@ -76,4 +76,4 @@ jobs: env: CTEST_OUTPUT_ON_FAILURE: 1 working-directory: ${{runner.workspace}}/build - run: ctest -C ${{matrix.build_type}} -j 2 + run: ctest -C ${{matrix.build_type}} From 1de7526fc01d0f173d18d0ea93f55af57b850d38 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 4 Jan 2021 14:31:44 +0100 Subject: [PATCH 53/90] Add a GitHub Action for Ubuntu builds The actions tests both g++ 5.5 and clang++ 6, with ubsan, asan and Valgrind memcheck. [ci skip] --- .github/workflows/build-ubuntu.yml | 101 +++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 .github/workflows/build-ubuntu.yml diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml new file mode 100644 index 00000000..e9a2b082 --- /dev/null +++ b/.github/workflows/build-ubuntu.yml @@ -0,0 +1,101 @@ +# Copyright (c) 2021 Morwenn +# SPDX-License-Identifier: MIT + +name: Ubuntu Builds + +on: + push: + paths: + - 'CMakeLists.txt' + - 'cmake/**' + - 'examples/**' + - 'include/**' + - 'testsuite/**' + pull_request: + paths: + - 'CMakeLists.txt' + - 'cmake/**' + - 'examples/**' + - 'include/**' + - 'testsuite/**' + +jobs: + build: + runs-on: ubuntu-16.04 + + strategy: + fail-fast: false + matrix: + # Default configuration corresponds to a release + # build without additional tooling, also provides + # default options for other builds + cxx: + - g++-5 + - clang++-6.0 + build_type: [Release] + sanitize: [''] + valgrind: [OFF] + + # Below are debug builds with tooling + include: + # GCC builds + - cxx: g++-5 + build_type: Debug + valgrind: ON + - cxx: g++-5 + build_type: Debug + sanitize: address + - cxx: g++-5 + build_type: Debug + sanitize: undefined + # Clang builds + - cxx: clang++-6.0 + build_type: Debug + valgrind: ON + - cxx: clang++-6.0 + build_type: Debug + sanitize: address + - cxx: clang++-6.0 + build_type: Debug + sanitize: undefined + + steps: + - uses: actions/checkout@v2 + + - name: Install Valgrind + if: ${{matrix.valgrind == 'ON'}} + run: sudo apt install -y valgrind + + - name: Configure CMake + working-directory: ${{runner.workspace}} + env: + CXX: ${{matrix.cxx}} + run: | + cmake -H${{github.event.repository.name}} -Bbuild \ + -DCMAKE_CONFIGURATION_TYPES=${{matrix.build_type}} \ + -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \ + -DSANITIZE=${{matrix.sanitize}} \ + -DUSE_VALGRIND=${{matrix.valgrind}} \ + -G"Unix Makefiles" \ + -DBUILD_EXAMPLES=ON + + - name: Build the test suite + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake --build . --config ${{matrix.build_type}} -j 2 + + - name: Run the test suite with Memcheck + if: ${{matrix.valgrind != 'ON'}} + env: + CTEST_OUTPUT_ON_FAILURE: 1 + working-directory: ${{runner.workspace}}/build + run: ctest -C ${{matrix.build_type}} + + - name: Run the test suite + if: ${{matrix.valgrind == 'ON'}} + env: + CTEST_OUTPUT_ON_FAILURE: 1 + working-directory: ${{runner.workspace}}/build + run: | + ctest -T memcheck -C ${{matrix.build_type}} -j 2 + find ./Testing/Temporary -name "MemoryChecker.*.log" -size +1300c | xargs cat; From 57545cbc1baf8c623c1da0fbcdcaebbf2820d85b Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 4 Jan 2021 14:53:37 +0100 Subject: [PATCH 54/90] CMake: find_program(valgrind REQUIRED) [ci skip] --- testsuite/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testsuite/CMakeLists.txt b/testsuite/CMakeLists.txt index e13fdc78..846c135b 100644 --- a/testsuite/CMakeLists.txt +++ b/testsuite/CMakeLists.txt @@ -220,7 +220,7 @@ endif() # Configure Valgrind if (USE_VALGRIND) - find_program(MEMORYCHECK_COMMAND valgrind) + find_program(MEMORYCHECK_COMMAND valgrind REQUIRED) set(MEMORYCHECK_COMMAND_OPTIONS "--leak-check=full --track-origins=yes --error-exitcode=1 --show-reachable=no") if (APPLE) set(MEMORYCHECK_SUPPRESSIONS_FILE ${CMAKE_CURRENT_SOURCE_DIR}/valgrind-osx.supp) From 86a86cc9cdbcdb6e2876e7327094f4d76bdd8490 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 4 Jan 2021 15:06:45 +0100 Subject: [PATCH 55/90] Ensure that some workflows are relaunch when their YAML is changed [ci skip] --- .github/workflows/build-macos.yml | 4 +++- .github/workflows/build-ubuntu.yml | 2 ++ .github/workflows/code-coverage.yml | 3 ++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 04a62544..7566638f 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2020-2021 Morwenn +# Copyright (c) 2021 Morwenn # SPDX-License-Identifier: MIT name: MacOS Builds @@ -6,6 +6,7 @@ name: MacOS Builds on: push: paths: + - '.github/workflows/build-macos.yml' - 'CMakeLists.txt' - 'cmake/**' - 'examples/**' @@ -13,6 +14,7 @@ on: - 'testsuite/**' pull_request: paths: + - '.github/workflows/build-macos.yml' - 'CMakeLists.txt' - 'cmake/**' - 'examples/**' diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index e9a2b082..fa93784f 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -6,6 +6,7 @@ name: Ubuntu Builds on: push: paths: + - '.github/workflows/build-ubuntu.yml' - 'CMakeLists.txt' - 'cmake/**' - 'examples/**' @@ -13,6 +14,7 @@ on: - 'testsuite/**' pull_request: paths: + - '.github/workflows/build-ubuntu.yml' - 'CMakeLists.txt' - 'cmake/**' - 'examples/**' diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index 4ca0160f..31f6f98e 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2020 Morwenn +# Copyright (c) 2020-2021 Morwenn # SPDX-License-Identifier: MIT name: Coverage Upload to Codecov @@ -10,6 +10,7 @@ on: - develop - 2.0.0-develop paths: + - '.github/workflows/code-coverage.yml' - 'CMakeLists.txt' - 'cmake/**' - 'codecov.yml' From c863ccfc964d98e9227050c63ced7aadec72e104 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 5 Jan 2021 09:41:38 +0100 Subject: [PATCH 56/90] Fix thinko in Ubuntu builds [ci skip] --- .github/workflows/build-ubuntu.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index fa93784f..6b98e978 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -86,14 +86,14 @@ jobs: working-directory: ${{runner.workspace}}/build run: cmake --build . --config ${{matrix.build_type}} -j 2 - - name: Run the test suite with Memcheck + - name: Run the test suite if: ${{matrix.valgrind != 'ON'}} env: CTEST_OUTPUT_ON_FAILURE: 1 working-directory: ${{runner.workspace}}/build run: ctest -C ${{matrix.build_type}} - - name: Run the test suite + - name: Run the test suite with Memcheck if: ${{matrix.valgrind == 'ON'}} env: CTEST_OUTPUT_ON_FAILURE: 1 From f3ec0a5a0eb7d992e2238c031acaf78253f4f258 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 5 Jan 2021 10:53:09 +0100 Subject: [PATCH 57/90] Add a GitHub Action for Windows builds (#176) --- .github/workflows/build-windows.yml | 54 +++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 .github/workflows/build-windows.yml diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml new file mode 100644 index 00000000..b7bf14e2 --- /dev/null +++ b/.github/workflows/build-windows.yml @@ -0,0 +1,54 @@ +# Copyright (c) 2021 Morwenn +# SPDX-License-Identifier: MIT + +name: Windows Builds + +on: + push: + paths: + - '.github/workflows/build-windows.yml' + - 'CMakeLists.txt' + - 'cmake/**' + - 'examples/**' + - 'include/**' + - 'testsuite/**' + pull_request: + paths: + - '.github/workflows/build-windows.yml' + - 'CMakeLists.txt' + - 'cmake/**' + - 'examples/**' + - 'include/**' + - 'testsuite/**' + +jobs: + build: + runs-on: windows-2019 + + strategy: + fail-fast: false + matrix: + build_type: [Debug, Release] + + steps: + - uses: actions/checkout@v2 + + - name: Configure CMake + shell: pwsh + working-directory: ${{runner.workspace}} + run: | + cmake -H${{github.event.repository.name}} -Bbuild ` + -DCMAKE_CONFIGURATION_TYPES=${{matrix.build_type}} ` + -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ` + -G"MinGW Makefiles" ` + -DBUILD_EXAMPLES=ON + + - name: Build the test suite + working-directory: ${{runner.workspace}}/build + run: cmake --build . --config ${{matrix.build_type}} -j 2 + + - name: Run the test suite + env: + CTEST_OUTPUT_ON_FAILURE: 1 + working-directory: ${{runner.workspace}}/build + run: ctest -C ${{matrix.build_type}} From 829cd03d369832e40789ca629c873dd98c2f1f08 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 5 Jan 2021 11:27:19 +0100 Subject: [PATCH 58/90] Remove files linked to Travis CI (#176) --- .travis.yml | 172 ------------------------------------ mtime_cache_globs.txt | 3 - testsuite/valgrind-osx.supp | 29 ------ 3 files changed, 204 deletions(-) delete mode 100644 .travis.yml delete mode 100644 mtime_cache_globs.txt delete mode 100644 testsuite/valgrind-osx.supp diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 8e015e7a..00000000 --- a/.travis.yml +++ /dev/null @@ -1,172 +0,0 @@ -# Copyright (c) 2015-2020 Morwenn -# SPDX-License-Identifier: MIT - -language: cpp - -_packages: - - &clang clang-3.8 - - &gcc g++-5 - -_apt: &apt-common - sources: - - llvm-toolchain-trusty-3.8 - - ubuntu-toolchain-r-test - -cache: - ccache: true - directories: - - build - - .mtime_cache - -matrix: - include: - - # Linux clang - - os: linux - sudo: required - env: BUILD_TYPE=Debug USE_VALGRIND=true CMAKE_GENERATOR="Unix Makefiles" - addons: - apt: - <<: *apt-common - packages: - - *clang - - *gcc - - valgrind - compiler: clang - - - os: linux - sudo: required - env: BUILD_TYPE=Debug SANITIZE=undefined CMAKE_GENERATOR="Unix Makefiles" - addons: - apt: - <<: *apt-common - packages: - - *clang - - *gcc - compiler: clang - - - os: linux - sudo: required - env: BUILD_TYPE=Debug SANITIZE=address CMAKE_GENERATOR="Unix Makefiles" - addons: - apt: - <<: *apt-common - packages: - - *clang - - *gcc - compiler: clang - - - os: linux - sudo: required - env: BUILD_TYPE=Release CMAKE_GENERATOR="Unix Makefiles" - addons: - apt: - <<: *apt-common - packages: - - *clang - - *gcc - compiler: clang - - # Linux gcc - - os: linux - sudo: false - env: BUILD_TYPE=Debug USE_VALGRIND=true CMAKE_GENERATOR="Unix Makefiles" - addons: - apt: - <<: *apt-common - packages: - - *gcc - - valgrind - compiler: gcc - - - os: linux - sudo: false - env: BUILD_TYPE=Debug SANITIZE=undefined CMAKE_GENERATOR="Unix Makefiles" - addons: - apt: - <<: *apt-common - packages: - - *gcc - compiler: gcc - - - os: linux - sudo: false - env: BUILD_TYPE=Debug SANITIZE=address CMAKE_GENERATOR="Unix Makefiles" - addons: - apt: - <<: *apt-common - packages: - - *gcc - compiler: gcc - - - os: linux - sudo: false - env: BUILD_TYPE=Release CMAKE_GENERATOR="Unix Makefiles" - addons: - apt: - <<: *apt-common - packages: - - *gcc - compiler: gcc - - # Windows GCC - - os: windows - language: sh - env: BUILD_TYPE=Debug CMAKE_GENERATOR="MinGW Makefiles" - compiler: gcc - - - os: windows - language: sh - env: BUILD_TYPE=Release CMAKE_GENERATOR="MinGW Makefiles" - compiler: gcc - -before_install: - - if [[ $TRAVIS_OS_NAME = "linux" && $CXX = "clang++" ]]; then - sudo ln -s $(which ccache) /usr/lib/ccache/clang++; - export CXXFLAGS="-Qunused-arguments"; - fi - -install: - - gem install mtime_cache - - if [[ $TRAVIS_OS_NAME = "osx" ]]; then - export PATH="/usr/local/opt/ccache/libexec:$PATH"; - fi - - if [[ $CXX = "g++" && $TRAVIS_OS_NAME != "osx" ]]; then export CXX="g++-5"; fi - - if [[ $CXX = "clang++" && $TRAVIS_OS_NAME != "osx" ]]; then export CXX="clang++-3.8"; fi - -script: - - cmake -H. -Bbuild - -DCMAKE_CONFIGURATION_TYPES="${BUILD_TYPE}" - -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" - -DSANITIZE="${SANITIZE}" - -DUSE_VALGRIND=${USE_VALGRIND} - -DENABLE_COVERAGE=${ENABLE_COVERAGE} - -G"${CMAKE_GENERATOR}" - -DCMAKE_SH="CMAKE_SH-NOTFOUND" - -DBUILD_EXAMPLES=ON - - mtime_cache -g mtime_cache_globs.txt -c .mtime_cache/cache.json - - if [[ $TRAVIS_OS_NAME = "osx" ]]; then - cmake --build build --config ${BUILD_TYPE}; - else - cmake --build build --config ${BUILD_TYPE} -- -j2; - fi - - cd build - - if [[ $USE_VALGRIND = true ]]; then - travis_wait 50 ctest -T memcheck -C ${BUILD_TYPE} --output-on-failure -j 4; - else - travis_wait ctest -C ${BUILD_TYPE} --output-on-failure; - fi - - if [[ $TRAVIS_OS_NAME = "windows" ]]; then - cd ..; - fi - -after_failure: - - if [[ $TRAVIS_OS_NAME = "windows" ]]; then - cd build; - fi - - if [[ $USE_VALGRIND = true ]]; then - find ./Testing/Temporary -type f -name "MemoryChecker.*.log" -size +1300c | xargs cat; - fi - -notifications: - email: false diff --git a/mtime_cache_globs.txt b/mtime_cache_globs.txt deleted file mode 100644 index 7b3da46f..00000000 --- a/mtime_cache_globs.txt +++ /dev/null @@ -1,3 +0,0 @@ -testsuite/**/*.{%{cpp}} -include/cpp-sort/**/*.{%{cpp}} -build/Catch2-src/single_include/catch2/catch.hpp diff --git a/testsuite/valgrind-osx.supp b/testsuite/valgrind-osx.supp deleted file mode 100644 index 569fd280..00000000 --- a/testsuite/valgrind-osx.supp +++ /dev/null @@ -1,29 +0,0 @@ -{ - Mac-OS-X-System-Leaks - Memcheck:Leak - match-leak-kinds: possible - fun:calloc - fun:map_images_nolock - fun:map_images - fun:_ZN4dyldL18notifyBatchPartialE17dyld_image_statesbPFPKcS0_jPK15dyld_image_infoEbb - fun:_ZN4dyld21registerObjCNotifiersEPFvjPKPKcPKPK11mach_headerEPFvS1_S6_ESC_ - fun:_dyld_objc_notify_register - fun:_objc_init - fun:_os_object_init - fun:libdispatch_init - fun:libSystem_initializer - fun:_ZN16ImageLoaderMachO18doModInitFunctionsERKN11ImageLoader11LinkContextE - fun:_ZN16ImageLoaderMachO16doInitializationERKN11ImageLoader11LinkContextE - fun:_ZN11ImageLoader23recursiveInitializationERKNS_11LinkContextEjPKcRNS_21InitializerTimingListERNS_15UninitedUpwardsE - fun:_ZN11ImageLoader23recursiveInitializationERKNS_11LinkContextEjPKcRNS_21InitializerTimingListERNS_15UninitedUpwardsE - fun:_ZN11ImageLoader19processInitializersERKNS_11LinkContextEjRNS_21InitializerTimingListERNS_15UninitedUpwardsE - fun:_ZN11ImageLoader15runInitializersERKNS_11LinkContextERNS_21InitializerTimingListE - fun:_ZN4dyld24initializeMainExecutableEv - fun:_ZN4dyld5_mainEPK12macho_headermiPPKcS5_S5_Pm - fun:_ZN13dyldbootstrap5startEPK12macho_headeriPPKclS2_Pm - fun:_dyld_start - obj:* - obj:* - obj:* - obj:* -} \ No newline at end of file From 0ad187d55c29ad717d72f1ab30a4b435e5da514e Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 5 Jan 2021 14:09:48 +0100 Subject: [PATCH 59/90] Cleanup README and its badges (#176) - Remove the license badge, redudant with GitHub UI - Better text for release and Conan badges - Remove the Travis CI badge - Add build badges to the compiler support section - Update the information about supported compilers --- README.md | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 153ce318..82c5affc 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ -[![Latest Release](https://img.shields.io/badge/release-cpp--sort%2F1.8.1-blue.svg)](https://github.com/Morwenn/cpp-sort/releases/tag/1.8.1) -[![Conan Package](https://img.shields.io/badge/conan-1.8.1-blue.svg)](https://conan.io/center/cpp-sort?version=1.8.1) -[![Build Status](https://travis-ci.org/Morwenn/cpp-sort.svg?branch=master)](https://travis-ci.org/Morwenn/cpp-sort) -[![License](https://img.shields.io/:license-mit-blue.svg)](https://doge.mit-license.org) +[![Latest Release](https://img.shields.io/badge/release-1.8.1-blue.svg)](https://github.com/Morwenn/cpp-sort/releases/tag/1.8.1) +[![Conan Package](https://img.shields.io/badge/conan-cpp--sort%2F1.8.1-blue.svg)](https://conan.io/center/cpp-sort?version=1.8.1) [![Code Coverage](https://codecov.io/gh/Morwenn/cpp-sort/branch/master/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, @@ -111,29 +109,33 @@ wiki page](https://github.com/Morwenn/cpp-sort/wiki/Benchmarks). # Compiler support & tooling +![Ubuntu builds status](https://github.com/Morwenn/cpp-sort/workflows/Ubuntu%20Builds/badge.svg?branch=develop) +![Windows builds status](https://github.com/Morwenn/cpp-sort/workflows/Windows%20Builds/badge.svg?branch=develop) +![MacOS builds status](https://github.com/Morwenn/cpp-sort/workflows/MacOS%20Builds/badge.svg?branch=develop) + **cpp-sort** currently requires C++14 support, and only works with g++5 and clang++3.8 or more recent versions of these compilers. So far, the library should work with the following compilers: -* g++5.5 or more recent -* MinGW-w64 g++5.5 or more recent -* clang++3.8 or more recent -* AppleClang shipping with Xcode 9.4 (used to work with older versions but they aren't tested anymore) -* It is notably tested with both libstdc++ and libc++ +* g++5.5 or more recent. It it know not to work with some older g++5 versions. +* clang++6 or more recent. It should work with clang++ versions all the way back to 3.8, but the CI pipeline doesn't have test for those anymore. +* The versions of MinGW-w64 and AppleClang equivalent to the compilers mentioned above. +* Clang is notably tested with both libstdc++ and libc++. -The compilers listed above are the ones tested specifically, and the library is also tested with -the most recent versions of those compilers on a regular basis. All the other compiler versions -in-between are untested, but should also work. Feel free to open an issue if it isn't the case. +The compilers listed above are the ones used by the CI pipeline, and the library is also tested +with the most recent versions of those compilers on a regular basis. All the other compiler +versions in-between are untested, but should also work. Feel free to open an issue if it isn't the +case. Last time I tried it did not work with the latest MSVC. Future development on the C++14 branch will try to remain compatible with the compiler versions listed above. I might try to make it -work with it in the future. +work with MSVC in the future. The features in the library might differ depending on the C++ version used and on the compiler extensions enabled. Those changes [are documented](https://github.com/Morwenn/cpp-sort/wiki/Changelog) in the wiki. -The main repository contains additional support for standard tooling such as CMake or Conan; -you can read more about those [in the wiki](https://github.com/Morwenn/cpp-sort/wiki/Tooling). +The main repository contains additional support for standard tooling such as CMake or Conan. +You can read more about those [in the wiki](https://github.com/Morwenn/cpp-sort/wiki/Tooling). # Thanks From 2cc9016a0830f70906b98ee194514ce75176dc89 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 5 Jan 2021 15:03:54 +0100 Subject: [PATCH 60/90] Simplify MacOS builds --- .github/workflows/build-macos.yml | 36 ++++++++++--------------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 7566638f..3ce5223c 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -28,30 +28,16 @@ jobs: strategy: fail-fast: false matrix: - # Default configuration corresponds to a release - # build without additional tooling, also provides - # default options for other builds cxx: - g++-9 - $(brew --prefix llvm)/bin/clang++ # Clang 11 - build_type: [Release] - sanitize: [''] - - # Below are debug builds with tooling - include: - # GCC builds - - cxx: g++-9 - build_type: Debug - sanitize: address - - cxx: g++-9 - build_type: Debug - sanitize: undefined - # Clang builds - - cxx: $(brew --prefix llvm)/bin/clang++ - build_type: Debug + config: + # Release build + - build_type: Release + # Debug builds + - build_type: Debug sanitize: address - - cxx: $(brew --prefix llvm)/bin/clang++ - build_type: Debug + - build_type: Debug sanitize: undefined steps: @@ -63,19 +49,19 @@ jobs: run: | export CXX=${{matrix.cxx}} cmake -H${{github.event.repository.name}} -Bbuild \ - -DCMAKE_CONFIGURATION_TYPES=${{matrix.build_type}} \ - -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \ - -DSANITIZE=${{matrix.sanitize}} \ + -DCMAKE_CONFIGURATION_TYPES=${{matrix.config.build_type}} \ + -DCMAKE_BUILD_TYPE=${{matrix.config.build_type}} \ + -DSANITIZE=${{matrix.config.sanitize}} \ -GNinja \ -DBUILD_EXAMPLES=ON - name: Build the test suite shell: bash working-directory: ${{runner.workspace}}/build - run: cmake --build . --config ${{matrix.build_type}} -j 2 + run: cmake --build . --config ${{matrix.config.build_type}} -j 2 - name: Run the test suite env: CTEST_OUTPUT_ON_FAILURE: 1 working-directory: ${{runner.workspace}}/build - run: ctest -C ${{matrix.build_type}} + run: ctest -C ${{matrix.config.build_type}} From 13157e2aa1fccc43d5eb0e7dffd40f431de64b71 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 5 Jan 2021 15:29:21 +0100 Subject: [PATCH 61/90] Simplify Ubuntu builds --- .github/workflows/build-ubuntu.yml | 53 ++++++++++-------------------- 1 file changed, 17 insertions(+), 36 deletions(-) diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index 6b98e978..efc56ee5 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -28,44 +28,25 @@ jobs: strategy: fail-fast: false matrix: - # Default configuration corresponds to a release - # build without additional tooling, also provides - # default options for other builds cxx: - g++-5 - clang++-6.0 - build_type: [Release] - sanitize: [''] - valgrind: [OFF] - - # Below are debug builds with tooling - include: - # GCC builds - - cxx: g++-5 - build_type: Debug - valgrind: ON - - cxx: g++-5 - build_type: Debug - sanitize: address - - cxx: g++-5 - build_type: Debug - sanitize: undefined - # Clang builds - - cxx: clang++-6.0 - build_type: Debug + config: + # Release build + - build_type: Release + # Debug builds + - build_type: Debug valgrind: ON - - cxx: clang++-6.0 - build_type: Debug + - build_type: Debug sanitize: address - - cxx: clang++-6.0 - build_type: Debug + - build_type: Debug sanitize: undefined steps: - uses: actions/checkout@v2 - name: Install Valgrind - if: ${{matrix.valgrind == 'ON'}} + if: ${{matrix.config.valgrind == 'ON'}} run: sudo apt install -y valgrind - name: Configure CMake @@ -74,30 +55,30 @@ jobs: CXX: ${{matrix.cxx}} run: | cmake -H${{github.event.repository.name}} -Bbuild \ - -DCMAKE_CONFIGURATION_TYPES=${{matrix.build_type}} \ - -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \ - -DSANITIZE=${{matrix.sanitize}} \ - -DUSE_VALGRIND=${{matrix.valgrind}} \ + -DCMAKE_CONFIGURATION_TYPES=${{matrix.config.build_type}} \ + -DCMAKE_BUILD_TYPE=${{matrix.config.build_type}} \ + -DSANITIZE=${{matrix.config.sanitize}} \ + -DUSE_VALGRIND=${{matrix.config.valgrind}} \ -G"Unix Makefiles" \ -DBUILD_EXAMPLES=ON - name: Build the test suite shell: bash working-directory: ${{runner.workspace}}/build - run: cmake --build . --config ${{matrix.build_type}} -j 2 + run: cmake --build . --config ${{matrix.config.build_type}} -j 2 - name: Run the test suite - if: ${{matrix.valgrind != 'ON'}} + if: ${{matrix.config.valgrind != 'ON'}} env: CTEST_OUTPUT_ON_FAILURE: 1 working-directory: ${{runner.workspace}}/build - run: ctest -C ${{matrix.build_type}} + run: ctest -C ${{matrix.config.build_type}} - name: Run the test suite with Memcheck - if: ${{matrix.valgrind == 'ON'}} + if: ${{matrix.config.valgrind == 'ON'}} env: CTEST_OUTPUT_ON_FAILURE: 1 working-directory: ${{runner.workspace}}/build run: | - ctest -T memcheck -C ${{matrix.build_type}} -j 2 + ctest -T memcheck -C ${{matrix.config.build_type}} -j 2 find ./Testing/Temporary -name "MemoryChecker.*.log" -size +1300c | xargs cat; From 385d8b926977a4d047fbeef147c009be16f81d0c Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 5 Jan 2021 15:31:10 +0100 Subject: [PATCH 62/90] Change codecov badge to target the develop branch --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 82c5affc..b9083411 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ [![Latest Release](https://img.shields.io/badge/release-1.8.1-blue.svg)](https://github.com/Morwenn/cpp-sort/releases/tag/1.8.1) [![Conan Package](https://img.shields.io/badge/conan-cpp--sort%2F1.8.1-blue.svg)](https://conan.io/center/cpp-sort?version=1.8.1) -[![Code Coverage](https://codecov.io/gh/Morwenn/cpp-sort/branch/master/graph/badge.svg)](https://codecov.io/gh/Morwenn/cpp-sort) +[![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, > regardless of application or the computer being used. But in fact, each method has its own From 2721ec74c9abf8bc1afa09b0c63fc95d4ab0f2c0 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 6 Jan 2021 13:41:04 +0100 Subject: [PATCH 63/90] Make comparison_counter conditionally branchless (#177) Drive-by fixes: - Remove useless default template parameters - std::move a projection in counting_adapter --- include/cpp-sort/adapters/counting_adapter.h | 4 +- include/cpp-sort/detail/comparison_counter.h | 85 +++++++++++--------- 2 files changed, 50 insertions(+), 39 deletions(-) diff --git a/include/cpp-sort/adapters/counting_adapter.h b/include/cpp-sort/adapters/counting_adapter.h index f553cea6..6107f637 100644 --- a/include/cpp-sort/adapters/counting_adapter.h +++ b/include/cpp-sort/adapters/counting_adapter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2019 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_ADAPTERS_COUNTING_ADAPTER_H_ @@ -82,7 +82,7 @@ namespace cppsort { CountType count(0); comparison_counter cmp(std::move(compare), count); - this->get()(std::forward(iterable), std::move(cmp), projection); + this->get()(std::forward(iterable), std::move(cmp), std::move(projection)); return count; } diff --git a/include/cpp-sort/detail/comparison_counter.h b/include/cpp-sort/detail/comparison_counter.h index 87bfea57..3601d042 100644 --- a/include/cpp-sort/detail/comparison_counter.h +++ b/include/cpp-sort/detail/comparison_counter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2017 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_COMPARISON_COUNTER_H_ @@ -8,47 +8,58 @@ //////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// -#include -#include #include #include +#include namespace cppsort { -namespace detail -{ - template< - typename Compare = std::less<>, - typename CountType = std::size_t - > - class comparison_counter + namespace detail + { + template + class comparison_counter + { + public: + + comparison_counter(Compare compare, CountType& count): + compare(std::move(compare)), + count(count) + {} + + template + auto operator()(T&& lhs, U&& rhs) + -> decltype(auto) + { + ++count; + auto&& comp = utility::as_function(compare); + return comp(std::forward(lhs), std::forward(rhs)); + } + + // Accessible member data + Compare compare; + + private: + + // Comparison functions are generally passed by value, + // therefore we need to know where is the original counter + // in order to increment it + CountType& count; + }; + } + + namespace utility { - public: - - comparison_counter(Compare compare, CountType& count): - compare(std::move(compare)), - count(count) - {} - - template - auto operator()(T&& lhs, U&& rhs) - -> decltype(auto) - { - ++count; - auto&& comp = utility::as_function(compare); - return comp(std::forward(lhs), std::forward(rhs)); - } - - // Accessible member data - Compare compare; - - private: - - // Comparison functions are generally passed by value, - // therefore we need to know where is the original counter - // in order to increment it - CountType& count; - }; -}} + template + struct is_probably_branchless_comparison< + cppsort::detail::comparison_counter, + T + >: + cppsort::detail::conjunction< + std::is_arithmetic, // Probably a safe enough bet + is_probably_branchless_comparison + > + {}; + } +} #endif // CPPSORT_DETAIL_COMPARISON_COUNTER_H_ From 78a93e98739c3f53554ed1e32a4194e691892c43 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 6 Jan 2021 23:45:59 +0100 Subject: [PATCH 64/90] Mark mountain sort internal projection as likely branchless (#177) indirect_adaper is implemented with mountain sort for random-access iterator. This commits turns internal lambdas used for projections into a proper named function object, and marks it as likely branchless when the projection it wraps is likely branchless itself. --- include/cpp-sort/adapters/indirect_adapter.h | 69 +++++++++++++++++--- 1 file changed, 59 insertions(+), 10 deletions(-) diff --git a/include/cpp-sort/adapters/indirect_adapter.h b/include/cpp-sort/adapters/indirect_adapter.h index cf55866d..f345a332 100644 --- a/include/cpp-sort/adapters/indirect_adapter.h +++ b/include/cpp-sort/adapters/indirect_adapter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2020 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_ADAPTERS_INDIRECT_ADAPTER_H_ @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -30,11 +31,61 @@ namespace cppsort { - //////////////////////////////////////////////////////////// - // Adapter + namespace detail + { + //////////////////////////////////////////////////////////// + // indirect_projection + + template + struct indirect_projection + { + explicit indirect_projection(Projection projection): + projection(std::move(projection)) + {} + + template + constexpr auto operator()(RandomAccessIterator it) const + -> decltype(auto) + { + auto&& proj = utility::as_function(projection); + return proj(*it); + } + + template + constexpr auto operator()(RandomAccessIterator it) + -> decltype(auto) + { + auto&& proj = utility::as_function(projection); + return proj(*it); + } + + Projection projection; + }; + + template + auto make_indirect_projection(Projection projection) + -> indirect_projection + { + return indirect_projection(std::move(projection)); + } + } + + namespace utility + { + template + struct is_probably_branchless_projection< + cppsort::detail::indirect_projection, + T + >: + is_probably_branchless_projection + {}; + } namespace detail { + //////////////////////////////////////////////////////////// + // Indirect sort functions + template auto sort_indirectly(std::forward_iterator_tag, Sorter&& sorter, ForwardIterator first, ForwardIterator last, @@ -55,7 +106,6 @@ namespace cppsort -> decltype(auto) { using utility::iter_move; - auto&& proj = utility::as_function(projection); //////////////////////////////////////////////////////////// // Indirectly sort the iterators @@ -76,9 +126,7 @@ namespace cppsort // Sort the iterators on pointed values std::forward(sorter)( iterators.get(), iterators.get() + size, std::move(compare), - [&proj](RandomAccessIterator it) -> decltype(auto) { - return proj(*it); - } + make_indirect_projection(std::move(projection)) ); #else // Work around the sorters that return void @@ -127,13 +175,14 @@ namespace cppsort return std::forward(sorter)( iterators.get(), iterators.get() + size, std::move(compare), - [&proj](RandomAccessIterator it) -> decltype(auto) { - return proj(*it); - } + make_indirect_projection(std::move(projection)) ); #endif } + //////////////////////////////////////////////////////////// + // Adapter + template struct indirect_adapter_impl: utility::adapter_storage, From 3b4dad08af38ae117853eee715e5ea63337caf1a Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 10 Jan 2021 10:47:45 +0100 Subject: [PATCH 65/90] Mark data_getter as likely branchless in schwartz_adapter (#177) --- include/cpp-sort/adapters/schwartz_adapter.h | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/include/cpp-sort/adapters/schwartz_adapter.h b/include/cpp-sort/adapters/schwartz_adapter.h index f135cda1..9f4eff59 100644 --- a/include/cpp-sort/adapters/schwartz_adapter.h +++ b/include/cpp-sort/adapters/schwartz_adapter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2020 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_ADAPTERS_SCHWARTZ_ADAPTER_H_ @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include "../detail/associate_iterator.h" @@ -41,7 +42,18 @@ namespace cppsort return (std::forward(value).data); } }; + } + + namespace utility + { + template + struct is_probably_branchless_projection: + std::true_type + {}; + } + namespace detail + { //////////////////////////////////////////////////////////// // Algorithm proper From b37bfbcdad5931cc88845fb47710b48feebe38f4 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 10 Jan 2021 13:07:08 +0100 Subject: [PATCH 66/90] ska_sort: don't feed a lambda to the pdqsort fallback (#177) The lambda was a leftover from when ska_sort still relied on std::sort as a fallback. Replacing it with std::less<> + the passed projection means that pdqsort might use its branchless partition algorithm when it makes sense. --- include/cpp-sort/detail/ska_sort.h | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/include/cpp-sort/detail/ska_sort.h b/include/cpp-sort/detail/ska_sort.h index 5081dad8..763d2faa 100644 --- a/include/cpp-sort/detail/ska_sort.h +++ b/include/cpp-sort/detail/ska_sort.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2019 Morwenn + * Copyright (c) 2017-2021 Morwenn * SPDX-License-Identifier: MIT */ @@ -18,13 +18,14 @@ #include #include #include +#include #include #include #include #include #include #include -#include +#include #include "attributes.h" #include "iterator_traits.h" #include "memcpy_cast.h" @@ -532,9 +533,7 @@ namespace detail -> void { auto&& proj = utility::as_function(projection); - pdqsort(std::move(begin), std::move(end), [&](auto&& l, auto&& r) { - return proj(l) < proj(r); - }, utility::identity{}); + pdqsort(std::move(begin), std::move(end), std::less<>{}, std::move(projection)); } template From dedda218833bf74c47fc33241cbbf67b211d80ac Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 10 Jan 2021 16:24:46 +0100 Subject: [PATCH 67/90] ska_sort: fix a bug when sorting signed __int128_t Due to a copy-paste error, ska_sort did not work properly with signed 128-bit integers: it probably sorted signed and unsigned values correctly, but not a mix of both. This commit also uses std::uintptr_t when available instead of std::size_t when sorting pointers, making it generally more correct. --- include/cpp-sort/detail/ska_sort.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/include/cpp-sort/detail/ska_sort.h b/include/cpp-sort/detail/ska_sort.h index 763d2faa..46ffbdaf 100644 --- a/include/cpp-sort/detail/ska_sort.h +++ b/include/cpp-sort/detail/ska_sort.h @@ -135,7 +135,7 @@ namespace detail #ifdef __SIZEOF_INT128__ inline auto to_unsigned_or_bool(__int128_t l) - -> unsigned long long + -> __uint128_t { return static_cast<__uint128_t>(l) + static_cast<__uint128_t>(__int128_t(1) << (CHAR_BIT * sizeof(__int128_t) - 1)); @@ -164,12 +164,21 @@ namespace detail return u ^ (sign_bit | 0x8000000000000000); } +#ifdef UINTPTR_MAX + template + auto to_unsigned_or_bool(T* ptr) + -> std::uintptr_t + { + return reinterpret_cast(ptr); + } +#else template auto to_unsigned_or_bool(T* ptr) -> std::size_t { return reinterpret_cast(ptr); } +#endif template auto unroll_loop_four_times(RandomAccessIterator begin, std::size_t iteration_count, From b7c207bb8e9f2790f03d888db29b84a74bac6330 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 10 Jan 2021 16:27:52 +0100 Subject: [PATCH 68/90] Add signed 128-bit tests for ska_sort --- testsuite/sorters/ska_sorter.cpp | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/testsuite/sorters/ska_sorter.cpp b/testsuite/sorters/ska_sorter.cpp index 3baf9630..8bd02722 100644 --- a/testsuite/sorters/ska_sorter.cpp +++ b/testsuite/sorters/ska_sorter.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2020 Morwenn + * Copyright (c) 2017-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -28,23 +28,31 @@ TEST_CASE( "ska_sorter tests", "[ska_sorter]" ) CHECK( std::is_sorted(vec.begin(), vec.end()) ); } -#ifdef __SIZEOF_INT128__ - SECTION( "sort with unsigned int128 iterable" ) + SECTION( "sort with unsigned int iterators" ) { - std::vector<__uint128_t> vec; + std::vector vec; distribution(std::back_inserter(vec), 100'000); + cppsort::ska_sort(vec.begin(), vec.end()); + CHECK( std::is_sorted(vec.begin(), vec.end()) ); + } + +#ifdef __SIZEOF_INT128__ + SECTION( "sort with int128 iterable" ) + { + std::vector<__int128_t> vec; + distribution(std::back_inserter(vec), 100'000, -10'000); cppsort::ska_sort(vec); CHECK( std::is_sorted(vec.begin(), vec.end()) ); } -#endif - SECTION( "sort with unsigned int iterators" ) + SECTION( "sort with unsigned int128 iterable" ) { - std::vector vec; + std::vector<__uint128_t> vec; distribution(std::back_inserter(vec), 100'000); - cppsort::ska_sort(vec.begin(), vec.end()); + cppsort::ska_sort(vec); CHECK( std::is_sorted(vec.begin(), vec.end()) ); } +#endif SECTION( "sort with float iterable" ) { From 1f7183123089cc4dbaa5b7590e809e0a06722e3b Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 12 Jan 2021 16:34:34 +0100 Subject: [PATCH 69/90] Revert "Mark mountain sort internal projection as likely branchless (#177)" This reverts commit 78a93e98739c3f53554ed1e32a4194e691892c43. --- include/cpp-sort/adapters/indirect_adapter.h | 69 +++----------------- 1 file changed, 10 insertions(+), 59 deletions(-) diff --git a/include/cpp-sort/adapters/indirect_adapter.h b/include/cpp-sort/adapters/indirect_adapter.h index f345a332..cf55866d 100644 --- a/include/cpp-sort/adapters/indirect_adapter.h +++ b/include/cpp-sort/adapters/indirect_adapter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2021 Morwenn + * Copyright (c) 2015-2020 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_ADAPTERS_INDIRECT_ADAPTER_H_ @@ -18,7 +18,6 @@ #include #include #include -#include #include #include #include @@ -31,61 +30,11 @@ namespace cppsort { - namespace detail - { - //////////////////////////////////////////////////////////// - // indirect_projection - - template - struct indirect_projection - { - explicit indirect_projection(Projection projection): - projection(std::move(projection)) - {} - - template - constexpr auto operator()(RandomAccessIterator it) const - -> decltype(auto) - { - auto&& proj = utility::as_function(projection); - return proj(*it); - } - - template - constexpr auto operator()(RandomAccessIterator it) - -> decltype(auto) - { - auto&& proj = utility::as_function(projection); - return proj(*it); - } - - Projection projection; - }; - - template - auto make_indirect_projection(Projection projection) - -> indirect_projection - { - return indirect_projection(std::move(projection)); - } - } - - namespace utility - { - template - struct is_probably_branchless_projection< - cppsort::detail::indirect_projection, - T - >: - is_probably_branchless_projection - {}; - } + //////////////////////////////////////////////////////////// + // Adapter namespace detail { - //////////////////////////////////////////////////////////// - // Indirect sort functions - template auto sort_indirectly(std::forward_iterator_tag, Sorter&& sorter, ForwardIterator first, ForwardIterator last, @@ -106,6 +55,7 @@ namespace cppsort -> decltype(auto) { using utility::iter_move; + auto&& proj = utility::as_function(projection); //////////////////////////////////////////////////////////// // Indirectly sort the iterators @@ -126,7 +76,9 @@ namespace cppsort // Sort the iterators on pointed values std::forward(sorter)( iterators.get(), iterators.get() + size, std::move(compare), - make_indirect_projection(std::move(projection)) + [&proj](RandomAccessIterator it) -> decltype(auto) { + return proj(*it); + } ); #else // Work around the sorters that return void @@ -175,14 +127,13 @@ namespace cppsort return std::forward(sorter)( iterators.get(), iterators.get() + size, std::move(compare), - make_indirect_projection(std::move(projection)) + [&proj](RandomAccessIterator it) -> decltype(auto) { + return proj(*it); + } ); #endif } - //////////////////////////////////////////////////////////// - // Adapter - template struct indirect_adapter_impl: utility::adapter_storage, From f57d8193a114c695367d3c32e581f72b70a29149 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 12 Jan 2021 16:50:41 +0100 Subject: [PATCH 70/90] Make TimSort::gallop* static Change originally proposed by @vedgy in timsort/cpp-Timsort#35 --- include/cpp-sort/detail/timsort.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/cpp-sort/detail/timsort.h b/include/cpp-sort/detail/timsort.h index 453fb438..a0b4702f 100644 --- a/include/cpp-sort/detail/timsort.h +++ b/include/cpp-sort/detail/timsort.h @@ -6,7 +6,7 @@ * - http://cr.openjdk.java.net/~martin/webrevs/openjdk7/timsort/raw_files/new/src/share/classes/java/util/TimSort.java * * Copyright (c) 2011 Fuji, Goro (gfx) . - * Copyright (c) 2015-2020 Morwenn. + * Copyright (c) 2015-2021 Morwenn. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to @@ -287,8 +287,8 @@ namespace detail } template - auto gallopLeft(T&& key, Iter const base, difference_type const len, difference_type const hint, - Compare compare, Projection projection) + static auto gallopLeft(T&& key, Iter const base, difference_type const len, difference_type const hint, + Compare compare, Projection projection) -> difference_type { CPPSORT_ASSERT(len > 0); @@ -345,8 +345,8 @@ namespace detail } template - auto gallopRight(T&& key, Iter const base, difference_type const len, difference_type const hint, - Compare compare, Projection projection) + static auto gallopRight(T&& key, Iter const base, difference_type const len, difference_type const hint, + Compare compare, Projection projection) -> difference_type { CPPSORT_ASSERT(len > 0); From ce4d60eeb0c65a9e9363d56ed1027d22c4e75358 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 17 Jan 2021 19:25:09 +0100 Subject: [PATCH 71/90] Remove unused variable in ska_sort --- include/cpp-sort/detail/ska_sort.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/cpp-sort/detail/ska_sort.h b/include/cpp-sort/detail/ska_sort.h index 46ffbdaf..1233c5be 100644 --- a/include/cpp-sort/detail/ska_sort.h +++ b/include/cpp-sort/detail/ska_sort.h @@ -541,7 +541,6 @@ namespace detail auto StdSortFallback(RandomAccessIterator begin, RandomAccessIterator end, Projection projection) -> void { - auto&& proj = utility::as_function(projection); pdqsort(std::move(begin), std::move(end), std::less<>{}, std::move(projection)); } From 5f1d25c0a8349c1211b7a1323ef76bdaa87c155a Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 17 Jan 2021 19:50:03 +0100 Subject: [PATCH 72/90] Synchronize timsort with upstream --- include/cpp-sort/detail/timsort.h | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/include/cpp-sort/detail/timsort.h b/include/cpp-sort/detail/timsort.h index a0b4702f..a3b21c0f 100644 --- a/include/cpp-sort/detail/timsort.h +++ b/include/cpp-sort/detail/timsort.h @@ -250,10 +250,6 @@ namespace detail iterator base2 = pending_[i + 1].base; difference_type len2 = pending_[i + 1].len; - CPPSORT_ASSERT(len1 > 0); - CPPSORT_ASSERT(len2 > 0); - CPPSORT_ASSERT(base1 + len1 == base2); - pending_[i].len = len1 + len2; if (i == stackSize - 3) { @@ -262,6 +258,16 @@ namespace detail pending_.pop_back(); + mergeConsecutiveRuns(base1, len1, base2, len2, std::move(compare), std::move(projection)); + } + + void mergeConsecutiveRuns(iterator base1, difference_type len1, iterator base2, difference_type len2, + Compare compare, Projection projection) + { + CPPSORT_ASSERT(len1 > 0); + CPPSORT_ASSERT(len2 > 0); + CPPSORT_ASSERT(base1 + len1 == base2); + difference_type const k = gallopRight(*base2, base1, len1, 0, compare, projection); CPPSORT_ASSERT(k >= 0); From bc1cb993a43367ba2d09e7811562fa4db669607f Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 19 Jan 2021 19:25:39 +0100 Subject: [PATCH 73/90] Code coverage: don't run gcov twice --- .github/workflows/code-coverage.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index 31f6f98e..aad77d66 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -53,6 +53,7 @@ jobs: run: make gcov - name: Upload coverage info - uses: codecov/codecov-action@v1.0.15 + uses: codecov/codecov-action@v1.2.1 with: directory: ${{runner.workspace}}/build + functionalities: gcov From 0e8c9067b8396c629a6434920aefbac7aab089cd Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 19 Jan 2021 19:44:22 +0100 Subject: [PATCH 74/90] Document library decisions wrt LWG3031 (#136) --- docs/Home.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/Home.md b/docs/Home.md index e27acd41..b9102cae 100644 --- a/docs/Home.md +++ b/docs/Home.md @@ -31,6 +31,14 @@ If the library throws any other exception, it will likely come from user code. T *New in version 1.4.0* +### Support for predicates accepting non-`const` parameters + +Following the resolution of the standard library issues [LWG3031][lwg3031], there is implementation freedom for standard library algorithms to accept predicates that take their arguments by non-`const` reference. It is the responsibility of the caller to ensure that, should the predicate accept its arguments by both `const` and non-`const` reference, the results are consistent between overloads. + +**cpp-sort** fully supports predicates that take their arguments via non-`const` reference provided that the guarantees outlined in the previous paragraph are respected. + +*New in version 1.7.0* + ## Library information & configuration ### Versioning @@ -75,4 +83,5 @@ If you ever feel that this wiki is incomplete, that it needs more examples or mo Hope you have fun! + [lwg3031]: https://wg21.link/LWG3031 [swappable]: https://en.cppreference.com/w/cpp/concepts/swappable From d050b2dbc99933eb8bea94753566c7c5699a09fa Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 19 Jan 2021 21:11:58 +0100 Subject: [PATCH 75/90] Improve tools to debug sorters --- benchmarks/benchmarking-tools/distributions.h | 27 ++++++++++ tools/test_failing_sorter.cpp | 53 ++++++++++++------- 2 files changed, 61 insertions(+), 19 deletions(-) diff --git a/benchmarks/benchmarking-tools/distributions.h b/benchmarks/benchmarking-tools/distributions.h index d08f10d8..bf528a77 100644 --- a/benchmarks/benchmarking-tools/distributions.h +++ b/benchmarks/benchmarking-tools/distributions.h @@ -303,6 +303,33 @@ namespace dist static constexpr const char* output = "alternating_16_values.txt"; }; + struct descending_plateau: + base_distribution + { + template + auto operator()(OutputIterator out, std::size_t size, Projection projection={}) const + -> void + { + auto&& proj = cppsort::utility::as_function(projection); + + std::size_t i = size; + while (i > 2 * size / 3) { + *out++ = proj(i); + --i; + } + while (i > size / 3) { + *out++ = proj(size / 2); + --i; + } + while (i > 0) { + *out++ = proj(i); + --i; + } + } + + static constexpr const char* output = "descending_plateau.txt"; + }; + struct inversions: base_distribution { diff --git a/tools/test_failing_sorter.cpp b/tools/test_failing_sorter.cpp index e484a32b..98ea2616 100644 --- a/tools/test_failing_sorter.cpp +++ b/tools/test_failing_sorter.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 Morwenn + * Copyright (c) 2019-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -9,6 +9,9 @@ #include #include #include + +#define CPPSORT_ENABLE_ASSERTIONS + #include #include #include @@ -44,7 +47,7 @@ struct shuffled_string: template void test(const char* name) { - const int size = 491; + const int size = 412; std::vector collection; auto distribution = shuffled_string{}; @@ -53,30 +56,42 @@ void test(const char* name) auto copy = collection; cppsort::quick_sort(std::begin(copy), std::end(copy)); - std::cout << std::boolalpha << name << '\n'; auto sorter = Sorter{}; sorter(collection); + + // Collect basic data + auto first_unsorted_it = std::is_sorted_until(std::begin(collection), std::end(collection)); + bool is_collection_sorted = first_unsorted_it == std::end(collection); + + // Display information + std::cout << std::boolalpha << name << std::endl; std::cout << "is the collection sorted? "; - std::cout << std::is_sorted(std::begin(collection), std::end(collection)) << '\n'; + std::cout << is_collection_sorted << std::endl; + if (not is_collection_sorted) { + std::cout << "position of the first unsorted element: " + << std::distance(std::begin(collection), first_unsorted_it) + << std::endl; + } std::cout << "is it the same as the one sorted with std::sort? "; - std::cout << (collection == copy) << '\n'; + std::cout << (collection == copy) << std::endl; std::cout << "were some elements altered? "; auto copy2 = collection; cppsort::quick_sort(std::begin(collection), std::end(collection)); - std::cout << (collection != copy) << '\n'; + std::cout << (collection != copy) << std::endl; + // Measures of presortedness std::cout << '\n' - << "dis: " << cppsort::probe::dis(copy2) << '\n' - << "enc: " << cppsort::probe::enc(copy2) << '\n' - << "exc: " << cppsort::probe::exc(copy2) << '\n' - << "ham: " << cppsort::probe::ham(copy2) << '\n' - << "inv: " << cppsort::probe::inv(copy2) << '\n' - << "max: " << cppsort::probe::max(copy2) << '\n' - << "mono: " << cppsort::probe::mono(copy2) << '\n' - << "osc: " << cppsort::probe::osc(copy2) << '\n' - << "par: " << cppsort::probe::par(copy2) << '\n' - << "rem: " << cppsort::probe::rem(copy2) << '\n' - << "runs: " << cppsort::probe::runs(copy2) << '\n' + << "dis: " << cppsort::probe::dis(copy2) << std::endl + << "enc: " << cppsort::probe::enc(copy2) << std::endl + << "exc: " << cppsort::probe::exc(copy2) << std::endl + << "ham: " << cppsort::probe::ham(copy2) << std::endl + << "inv: " << cppsort::probe::inv(copy2) << std::endl + << "max: " << cppsort::probe::max(copy2) << std::endl + << "mono: " << cppsort::probe::mono(copy2) << std::endl + << "osc: " << cppsort::probe::osc(copy2) << std::endl + << "par: " << cppsort::probe::par(copy2) << std::endl + << "rem: " << cppsort::probe::rem(copy2) << std::endl + << "runs: " << cppsort::probe::runs(copy2) << std::endl << '\n'; if (size < 40) { @@ -84,11 +99,11 @@ void test(const char* name) for (const auto& elem: copy2) { std::cout << elem << ' '; } - std::cout << "\n\n"; + std::cout << "\n\n" << std::flush; } } int main() { - test>("verge_sort"); + test("poplar_sort"); } From bc76f827c62ecad1740262441bc9ca739b72cfc4 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 24 Jan 2021 18:59:25 +0100 Subject: [PATCH 76/90] Change the random-access version to nth_element to QuickSelect (#179) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This makes quick_merge_sort O(n log n) instead of O(n²) like it used to be (the libc++ implementation of std::nth_element used until then was quadratic). The new implementation was taken from Danila Kutenin's miniselect library. --- NOTICE.txt | 13 + README.md | 4 + docs/Sorters.md | 16 +- .../cpp-sort/detail/adaptive_quickselect.h | 500 ++++++++++++++++++ include/cpp-sort/detail/nth_element.h | 267 +--------- 5 files changed, 546 insertions(+), 254 deletions(-) create mode 100644 include/cpp-sort/detail/adaptive_quickselect.h diff --git a/NOTICE.txt b/NOTICE.txt index 3e65e05c..57f2babe 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -213,6 +213,19 @@ In addition, certain files include the notices provided below. ---------------------- +/* Copyright Andrei Alexandrescu, 2016-. + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * https://boost.org/LICENSE_1_0.txt) + */ +/* Copyright Danila Kutenin, 2020-. + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * https://boost.org/LICENSE_1_0.txt) + */ + +---------------------- + # Distributed under the OSI-approved MIT License. See accompanying # file LICENSE or https://github.com/Crascit/DownloadProject for details. diff --git a/README.md b/README.md index b9083411..248fd261 100644 --- a/README.md +++ b/README.md @@ -203,6 +203,10 @@ of the algorithm. * The algorithm used by `indirect_adapter` with forward or bidirectional iterators is a slightly modified version of Matthew Bentley's [indiesort](https://github.com/mattreecebentley/plf_indiesort). +* The implementation of the random-access overload of `nth_element` used by some of the algorithms +comes from Danila Kutenin's [miniselect library](https://github.com/danlark1/miniselect) and uses +Andrei Alexandrescu's [*AdaptiveQuickselect*](https://arxiv.org/abs/1606.00484) algorithm. + * The algorithms 0 to 16 used by `sorting_network_sorter` have been generated with Perl's [`Algorithm::Networksort` module](https://metacpan.org/pod/release/JGAMBLE/Algorithm-Networksort-1.30/lib/Algorithm/Networksort.pm). diff --git a/docs/Sorters.md b/docs/Sorters.md index 85df3309..7bb3e92f 100644 --- a/docs/Sorters.md +++ b/docs/Sorters.md @@ -223,7 +223,7 @@ This sorter is a bit faster or a bit slower than `smooth_sorter` depending on th #include ``` -Implements a flavour of [QuickMergesort](https://arxiv.org/abs/1307.3033). +Implements a flavour of [QuickMergesort][quick-mergesort]. | Best | Average | Worst | Memory | Stable | Iterators | | ----------- | ----------- | ----------- | ----------- | ----------- | ------------- | @@ -231,14 +231,18 @@ Implements a flavour of [QuickMergesort](https://arxiv.org/abs/1307.3033). | n | n log n | n log n | log² n | No | Bidirectional | | n | n log² n | n log² n | log² n | No | Forward | -QuickMergesort is an algorithm that performs a quicksort-like partition and tries to use mergesort on the bigger partition, using the smaller one as a swap buffer used for the merge operation when possible. The flavour of QuickMergesort used by `quick_merge_sorter` actually uses an equivalent of [`std::nth_element`](https://en.cppreference.com/w/cpp/algorithm/nth_element) to partition the collection in 1/3-2/3 parts in order to maximize the size of the partition (2 thirds of the space) that can be merge-sorted using the other partition as a swap buffer. +QuickMergesort is an algorithm that performs a quicksort-like partition and tries to use mergesort on the bigger partition, using the smaller one as a swap buffer used for the merge operation when possible. The flavour of QuickMergesort used by `quick_merge_sorter` uses a [selection algorithm][selection-algorithm] to split the collection into partitions containing 2/3 and 1/3 of the elements respectively. This allows to use an internal mergesort of the biggest partition (2/3 of the elements) using the other partition (1/3 of the elements) as a swap buffer. -The change in time complexity for forward iterators is due to the partitioning algorithm being O(n log n) instead of O(n). The log n memory is due to top-down mergesort stack recursion in the random-access version, while the memory of the forward version use is dominated by the mutually recursive [introselect](https://en.wikipedia.org/wiki/Introselect) algorithm which is used to implement an `nth_element` equivalent for forward iterators. +The change in time complexity for forward iterators is due to the partitioning algorithm being O(n log n) instead of O(n). The space complexity is dominated by the stack recursion in the selection algorithms: +* log n for the random-access version, which uses Andrei Alexandrescu's [*AdaptiveQuickselect*][adaptive-quickselect]. +* log² n for the forward and bidirectional versions, which use the mutually recursive [introselect][introselect] algorithm. This sorter can't throw `std::bad_alloc`. *New in version 1.2.0* +*Changed in version 1.9.0:* the random-access version now runs in O(n log n) instead of accidentally running in O(n²). + ### `quick_sorter` ```cpp @@ -474,3 +478,9 @@ struct spread_sorter: ``` *Changed in version 1.9.0:* conditional support for [`std::ranges::greater`](https://en.cppreference.com/w/cpp/utility/functional/ranges/greater). + + + [adaptive-quickselect]: https://arxiv.org/abs/1606.00484 + [introselect]: https://en.wikipedia.org/wiki/Introselect + [quick-mergesort]: https://arxiv.org/abs/1307.3033 + [selection-algorithm]: https://en.wikipedia.org/wiki/Selection_algorithm diff --git a/include/cpp-sort/detail/adaptive_quickselect.h b/include/cpp-sort/detail/adaptive_quickselect.h new file mode 100644 index 00000000..1736c337 --- /dev/null +++ b/include/cpp-sort/detail/adaptive_quickselect.h @@ -0,0 +1,500 @@ +/* + * Copyright (c) 2021 Morwenn + * SPDX-License-Identifier: MIT + */ + +/* Copyright Andrei Alexandrescu, 2016-. + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * https://boost.org/LICENSE_1_0.txt) + */ +/* Copyright Danila Kutenin, 2020-. + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * https://boost.org/LICENSE_1_0.txt) + */ +#ifndef CPPSORT_DETAIL_QUICKSELECT_ADAPTIVE_H_ +#define CPPSORT_DETAIL_QUICKSELECT_ADAPTIVE_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +#include +#include +#include "config.h" +#include "type_traits.h" + +namespace cppsort +{ +namespace detail +{ + namespace median_common_detail + { + template + auto pivot_partition(RandomAccessIterator r, difference_type_t k, + difference_type_t length, + Compare compare, Projection projection) + -> RandomAccessIterator + { + using utility::iter_swap; + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + CPPSORT_ASSERT(k < length); + iter_swap(r, r + k); + difference_type_t lo = 1, hi = length - 1; + for (;; ++lo, --hi) { + for (;; ++lo) { + if (lo > hi) { + goto loop_done; + } + if (not comp(proj(r[lo]), proj(*r))) break; + } + // found the left bound: r[lo] >= r[0] + CPPSORT_ASSERT(lo <= hi); + for (; comp(proj(*r), proj(r[hi])) ; --hi) {} + if (lo >= hi) break; + // found the right bound: r[hi] <= r[0], swap & make progress + iter_swap(r + lo, r + hi); + } + loop_done: + --lo; + iter_swap(r + lo, r); + return r + lo; + } + + template + auto median_index(RandomAccessIterator r, difference_type_t a, + difference_type_t b, difference_type_t c, + Compare compare, Projection projection) + -> difference_type_t + { + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + if (comp(proj(r[c]), proj(r[a]))) { + std::swap(a, c); + } + if (comp(proj(r[c]), proj(r[b]))) { + return c; + } + if (comp(proj(r[b]), proj(r[a]))) { + return a; + } + return b; + } + + template + auto median_index(RandomAccessIterator r, + difference_type_t a, difference_type_t b, + difference_type_t c, difference_type_t d, + Compare compare, Projection projection) + -> difference_type_t + { + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + if (comp(proj(r[d]), proj(r[c]))) { + std::swap(c, d); + } + + if (LeanRight) { + if (comp(proj(r[c]), proj(r[a]))) { + CPPSORT_ASSERT(comp(proj(r[c]), proj(r[a])) && not comp(proj(r[d]), proj(r[c]))); // so r[c]) is out + return median_index(r, a, b, d, std::move(compare), std::move(projection)); + } + } else { + if (not comp(proj(r[d]), proj(r[a]))) { + return median_index(r, a, b, c, std::move(compare), std::move(projection)); + } + } + + // Could return median_index(r, b, c, d) but we already know r[c] <= r[d] + if (not comp(proj(r[c]), proj(r[b]))) { + return c; + } + if (comp(proj(r[d]), proj(r[b]))) { + return d; + } + return b; + } + + template + auto ninther(RandomAccessIterator r, difference_type_t _1, difference_type_t _2, + difference_type_t _3, difference_type_t _4, + difference_type_t _5, difference_type_t _6, + difference_type_t _7, difference_type_t _8, + difference_type_t _9, Compare compare, Projection projection) + -> void + { + using utility::iter_swap; + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + _2 = median_index(r, _1, _2, _3, compare, projection); + _8 = median_index(r, _7, _8, _9, compare, projection); + if (comp(proj(r[_8]), proj(r[_2]))) { + std::swap(_2, _8); + } + if (comp(proj(r[_6]), proj(r[_4]))) { + std::swap(_4, _6); + } + + // Here we know that r[_2] and r[_8] are the other two medians and that + // r[_2] <= r[_8]. We also know that r[_4] <= r[_6] + if (comp(proj(r[_5]), proj(r[_4]))) { + // r[_4] is the median of r[_4], r[_5], r[_6] + } else if (comp(proj(r[_6]), proj(r[_5]))) { + // r[_6] is the median of r[_4], r[_5], r[_6] + _4 = _6; + } else { + // Here we know r[_5] is the median of r[_4], r[_5], r[_6] + if (comp(proj(r[_5]), proj(r[_2]))) { + iter_swap(r + _5, r + _2); + return; + } + if (comp(proj(r[_8]), proj(r[_5]))) { + iter_swap(r + _5, r + _8); + return; + } + // This is the only path that returns with no swap + return; + } + + // Here we know r[_4] is the median of r[_4], r[_5], r[_6] + if (comp(proj(r[_4]), proj(r[_2]))) { + _4 = _2; + } else if (comp(proj(r[_8]), proj(r[_4]))) { + _4 = _8; + } + iter_swap(r + _5, r + _4); + } + + template + auto expand_partition_left(RandomAccessIterator r, difference_type_t lo, + difference_type_t pivot, + Compare compare, Projection projection) + -> difference_type_t + { + using utility::iter_swap; + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + CPPSORT_ASSERT(lo > 0 && lo <= pivot); + difference_type_t left = 0; + const auto oldPivot = pivot; + for (; lo < pivot ; ++left) { + if (left == lo) { + goto done; + } + if (not comp(proj(r[oldPivot]), proj(r[left]))) continue; + --pivot; + iter_swap(r + left, r + pivot); + } + + // Second loop: make left and pivot meet + for (;; ++left) { + if (left == pivot) break; + if (not comp(proj(r[oldPivot]), proj(r[left]))) continue; + for (;;) { + if (left == pivot) { + goto done; + } + --pivot; + if (comp(proj(r[pivot]), proj(r[oldPivot]))) { + iter_swap(r + left, r + pivot); + break; + } + } + } + + done: + iter_swap(r + oldPivot, r + pivot); + return pivot; + } + + template + auto expand_partition_right(RandomAccessIterator r, difference_type_t hi, + difference_type_t rite, + Compare compare, Projection projection) + -> difference_type_t + { + using utility::iter_swap; + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + difference_type_t pivot = 0; + CPPSORT_ASSERT(pivot <= hi); + CPPSORT_ASSERT(hi <= rite); + + // First loop: spend r[pivot .. hi] + for (; pivot < hi ; --rite) { + if (rite == hi) { + goto done; + } + if (not comp(proj(r[rite]), proj(r[0]))) continue; + ++pivot; + iter_swap(r + rite, r + pivot); + } + + // Second loop: make left and pivot meet + for (; rite > pivot ; --rite) { + if (not comp(proj(r[rite]), proj(r[0]))) continue; + while (rite > pivot) { + ++pivot; + if (comp(proj(r[0]), proj(r[pivot]))) { + iter_swap(r + rite, r + pivot); + break; + } + } + } + + done: + iter_swap(r, r + pivot); + return pivot; + } + + template + auto expand_partition(RandomAccessIterator r, difference_type_t lo, + difference_type_t pivot, difference_type_t hi, + difference_type_t length, Compare compare, Projection projection) + -> difference_type_t + { + using utility::iter_swap; + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + CPPSORT_ASSERT(lo <= pivot && pivot < hi && hi <= length); + --hi; + --length; + + for (difference_type_t left = 0 ; ; ++left, --length) { + for (;; ++left) { + if (left == lo) { + return pivot + expand_partition_right(r + pivot, hi - pivot, length - pivot, + std::move(compare), std::move(projection)); + } + if (comp(proj(r[pivot]), proj(r[left]))) break; + } + for (;; --length) { + if (length == hi) { + return left + expand_partition_left(r + left, lo - left, pivot - left, + std::move(compare), std::move(projection)); + } + if (not comp(proj(r[pivot]), proj(r[length]))) break; + } + iter_swap(r + left, r + length); + } + } + } + + template + auto adaptive_quickselect(RandomAccessIterator r, difference_type_t n, + difference_type_t length, + Compare compare, Projection projection) + -> void; + + /* + Median of minima + */ + template + auto median_of_minima(RandomAccessIterator r, difference_type_t n, + difference_type_t length, + Compare compare, Projection projection) + -> difference_type_t + { + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + CPPSORT_ASSERT(length >= 2); + CPPSORT_ASSERT(n <= length / 6); + CPPSORT_ASSERT(n > 0); + + auto subset = n * 2; + auto computeMinOver = (length - subset) / subset; + CPPSORT_ASSERT(computeMinOver > 0); + + for (difference_type_t i = 0, j = subset ; i < subset ; ++i) { + auto limit = j + computeMinOver; + auto minIndex = j; + while (++j < limit) { + if (comp(proj(r[j]), proj(r[minIndex]))) { + minIndex = j; + } + } + if (comp(proj(r[minIndex]), proj(r[i]))) { + using utility::iter_swap; + iter_swap(r + i, r + minIndex); + } + CPPSORT_ASSERT(j < length || i + 1 == subset); + } + + adaptive_quickselect(r, n, subset, compare, projection); + return median_common_detail::expand_partition(r, 0, n, subset, length, + std::move(compare), std::move(projection)); + } + + /* + Median of maxima + */ + template + auto median_of_maxima(RandomAccessIterator r, difference_type_t n, + difference_type_t length, + Compare compare, Projection projection) + -> difference_type_t + { + using utility::iter_swap; + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + CPPSORT_ASSERT(length >= 2); + CPPSORT_ASSERT(n < length && n / 5 >= length - n); + + auto subset = (length - n) * 2; + auto subsetStart = length - subset; + auto computeMaxOver = subsetStart / subset; + CPPSORT_ASSERT(computeMaxOver > 0); + + for (auto i = subsetStart, j = i - subset * computeMaxOver ; i < length ; ++i) { + auto limit = j + computeMaxOver; + auto maxIndex = j; + while (++j < limit) { + if (comp(proj(r[maxIndex]), proj(r[j]))) { + maxIndex = j; + } + } + if (comp(proj(r[i]), proj(r[maxIndex]))) { + iter_swap(r + i, r + maxIndex); + } + CPPSORT_ASSERT(j != 0 || i + 1 == length); + } + + adaptive_quickselect(r + subsetStart, length - n, subset, compare, projection); + return median_common_detail::expand_partition(r, subsetStart, n, length, length, + std::move(compare), std::move(projection)); + } + + /* + Partitions r[0 .. length] using a pivot of its own choosing. Attempts to pick a + pivot that approximates the median. Returns the position of the pivot. + */ + template + auto median_of_ninthers(RandomAccessIterator r, difference_type_t length, + Compare compare, Projection projection) + -> difference_type_t + { + CPPSORT_ASSERT(length >= 12); + auto frac = length <= 1024 ? length / 12 + : length <= 128 * 1024 ? length / 64 + : length / 1024; + auto pivot = frac / 2; + auto lo = length / 2 - pivot; + auto hi = lo + frac; + CPPSORT_ASSERT(lo >= frac * 4); + CPPSORT_ASSERT(length - hi >= frac * 4); + CPPSORT_ASSERT(lo / 2 >= pivot); + + auto gap = (length - 9 * frac) / 4; + auto a = lo - 4 * frac - gap; + auto b = hi + gap; + for (auto i = lo ; i < hi ; ++i, a += 3, b += 3) { + median_common_detail::ninther(r, + a, i - frac, b, + a + 1, i, b + 1, + a + 2, i + frac, b + 2, + compare, projection + ); + } + + adaptive_quickselect(r + lo, pivot, frac, compare, projection); + return median_common_detail::expand_partition( + r, lo, lo + pivot, hi, length, + std::move(compare), std::move(projection) + ); + } + + /* + Quickselect driver for median_of_ninthers, median_of_minima, and + median_of_maxima. Dispathes to each depending on the relationship between n (the + sought order statistics) and length. + */ + template + auto adaptive_quickselect(RandomAccessIterator r, difference_type_t n, + difference_type_t length, + Compare compare, Projection projection) + -> void + { + using utility::iter_swap; + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + CPPSORT_ASSERT(n < length); + for (;;) { + // Decide strategy for partitioning + if (n == 0) { + // That would be the max + difference_type_t pivot = n; + for (++n ; n < length ; ++n) { + if (comp(proj(r[n]), proj(r[pivot]))) { + pivot = n; + } + } + iter_swap(r, r + pivot); + return; + } + + if (n + 1 == length) { + // That would be the min + difference_type_t pivot = 0; + for (n = 1 ; n < length ; ++n) { + if (comp(proj(r[pivot]), proj(r[n]))) { + pivot = n; + } + } + iter_swap(r + pivot, r + (length - 1)); + return; + } + + CPPSORT_ASSERT(n < length); + difference_type_t pivot; + if (length <= 16) { + pivot = median_common_detail::pivot_partition(r, n, length, compare, projection) - r; + } else if (n <= length / 6) { + pivot = median_of_minima(r, n, length, compare, projection); + } else if (n / 5 >= length - n) { + pivot = median_of_maxima(r, n, length, compare, projection); + } else { + pivot = median_of_ninthers(r, length, compare, projection); + } + + // See how the pivot fares + if (pivot == n) return; + if (pivot > n) { + length = pivot; + } else { + ++pivot; + r += pivot; + length -= pivot; + n -= pivot; + } + } + } + + template + auto median_of_ninthers_select(RandomAccessIterator begin, RandomAccessIterator mid, RandomAccessIterator end, + Compare compare, Projection projection) + -> RandomAccessIterator + { + if (mid != end) { + adaptive_quickselect(begin, mid - begin, end - begin, compare, projection); + } + return mid; + } +}} + +#endif // CPPSORT_DETAIL_QUICKSELECT_ADAPTIVE_H_ diff --git a/include/cpp-sort/detail/nth_element.h b/include/cpp-sort/detail/nth_element.h index d4f1af2c..4c8db6a8 100644 --- a/include/cpp-sort/detail/nth_element.h +++ b/include/cpp-sort/detail/nth_element.h @@ -1,16 +1,7 @@ /* - * Copyright (c) 2018 Morwenn + * Copyright (c) 2018-2021 Morwenn * SPDX-License-Identifier: MIT */ - -//===----------------------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - #ifndef CPPSORT_DETAIL_NTH_ELEMENT_H_ #define CPPSORT_DETAIL_NTH_ELEMENT_H_ @@ -19,11 +10,9 @@ //////////////////////////////////////////////////////////// #include #include -#include -#include +#include "adaptive_quickselect.h" #include "introselect.h" #include "iterator_traits.h" -#include "selection_sort.h" namespace cppsort { @@ -33,11 +22,11 @@ namespace detail // nth_element for forward iterators with introselect template - auto nth_element(ForwardIterator first, ForwardIterator last, + auto nth_element(std::forward_iterator_tag, + ForwardIterator first, ForwardIterator last, difference_type_t nth_pos, difference_type_t size, - Compare compare, Projection projection, - std::forward_iterator_tag) + Compare compare, Projection projection) -> ForwardIterator { return introselect(first, last, nth_pos, size, detail::log2(size), @@ -45,250 +34,27 @@ namespace detail } //////////////////////////////////////////////////////////// - // nth_element for random-access iterators from libc++ - - template - auto sort3(ForwardIterator x, ForwardIterator y, ForwardIterator z, - Compare compare, Projection projection) - -> unsigned - { - using utility::iter_swap; - - auto&& comp = utility::as_function(compare); - auto&& proj = utility::as_function(projection); - - unsigned r = 0; - if (not comp(proj(*y), proj(*x))) // if x <= y - { - if (not comp(proj(*z), proj(*y))) // if y <= z - return r; // x <= y && y <= z - // x <= y && y > z - iter_swap(y, z); // x <= z && y < z - r = 1; - if (comp(proj(*y), proj(*x))) // if x > y - { - iter_swap(x, y); // x < y && y <= z - r = 2; - } - return r; // x <= y && y < z - } - if (comp(proj(*z), proj(*y))) // x > y, if y > z - { - iter_swap(x, z); // x < y && y < z - r = 1; - return r; - } - iter_swap(x, y); // x > y && y <= z - r = 1; // x < y && x <= z - if (comp(proj(*z), proj(*y))) // if y > z - { - iter_swap(y, z); // x <= y && y < z - r = 2; - } - return r; - } // x <= y && y <= z + // nth_element for random-access iterators with Andrei + // Alexandrescu's adaptive quickselect template - auto nth_element(RandomAccessIterator first, RandomAccessIterator last, + auto nth_element(std::random_access_iterator_tag, + RandomAccessIterator first, RandomAccessIterator last, difference_type_t nth_pos, difference_type_t, // unused - Compare compare, Projection projection, - std::random_access_iterator_tag) + Compare compare, Projection projection) -> RandomAccessIterator { - using utility::iter_swap; - - auto&& comp = utility::as_function(compare); - auto&& proj = utility::as_function(projection); - - using difference_type = difference_type_t; - constexpr difference_type limit = 7; - - auto nth = first + nth_pos; - - while (true) - { - restart: - if (nth == last) - return nth; - difference_type len = last - first; - switch (len) - { - case 0: - case 1: - return nth; - case 2: - if (comp(proj(*--last), proj(*first))) { - iter_swap(first, last); - } - return nth; - case 3: - RandomAccessIterator m = first; - sort3(first, ++m, --last, std::move(compare), std::move(projection)); - return nth; - } - if (len <= limit) { - selection_sort(first, last, std::move(compare), std::move(projection)); - return nth; - } - // len > limit >= 3 - RandomAccessIterator m = first + len / 2; - RandomAccessIterator lm1 = last; - unsigned n_swaps = sort3(first, m, --lm1, compare, projection); - // *m is median - // partition [first, m) < *m and *m <= [m, last) - // (this inhibits tossing elements equivalent to m around unnecessarily) - RandomAccessIterator i = first; - RandomAccessIterator j = lm1; - // j points beyond range to be tested, *lm1 is known to be <= *m - // The search going up is known to be guarded but the search coming down isn't. - // Prime the downward search with a guard. - if (not comp(proj(*i), proj(*m))) // if *first == *m - { - // *first == *m, *first doesn't go in first part - // manually guard downward moving j against i - while (true) - { - if (i == --j) - { - // *first == *m, *m <= all other elements - // Parition instead into [first, i) == *first and *first < [i, last) - ++i; // first + 1 - j = last; - if (not comp(proj(*first), proj(*--j))) // we need a guard if *first == *(last-1) - { - while (true) - { - if (i == j) - return nth; // [first, last) all equivalent elements - if (comp(proj(*first), proj(*i))) - { - iter_swap(i, j); - ++n_swaps; - ++i; - break; - } - ++i; - } - } - // [first, i) == *first and *first < [j, last) and j == last - 1 - if (i == j) - return nth; - while (true) - { - while (not comp(proj(*first), proj(*i))) - ++i; - while (comp(proj(*first), proj(*--j))) - ; - if (i >= j) - break; - iter_swap(i, j); - ++n_swaps; - ++i; - } - // [first, i) == *first and *first < [i, last) - // The first part is sorted, - if (nth < i) - return nth; - // nth_element the second part - // nth_element(i, nth, last, comp); - first = i; - goto restart; - } - if (comp(proj(*j), proj(*m))) - { - iter_swap(i, j); - ++n_swaps; - break; // found guard for downward moving j, now use unguarded partition - } - } - } - ++i; - // j points beyond range to be tested, *lm1 is known to be <= *m - // if not yet partitioned... - if (i < j) - { - // known that *(i - 1) < *m - while (true) - { - // m still guards upward moving i - while (comp(proj(*i), proj(*m))) - ++i; - // It is now known that a guard exists for downward moving j - while (not comp(proj(*--j), proj(*m))) - ; - if (i >= j) - break; - iter_swap(i, j); - ++n_swaps; - // It is known that m != j - // If m just moved, follow it - if (m == i) - m = j; - ++i; - } - } - // [first, i) < *m and *m <= [i, last) - if (i != m && comp(proj(*m), proj(*i))) - { - iter_swap(i, m); - ++n_swaps; - } - // [first, i) < *i and *i <= [i+1, last) - if (nth == i) - return nth; - if (n_swaps == 0) - { - // We were given a perfectly partitioned sequence. Coincidence? - if (nth < i) - { - // Check for [first, i) already sorted - j = m = first; - while (++j != i) - { - if (comp(proj(*j), proj(*m))) - // not yet sorted, so sort - goto not_sorted; - m = j; - } - // [first, i) sorted - return nth; - } - else - { - // Check for [i, last) already sorted - j = m = i; - while (++j != last) - { - if (comp(proj(*j), proj(*m))) - // not yet sorted, so sort - goto not_sorted; - m = j; - } - // [i, last) sorted - return nth; - } - } - not_sorted: - // nth_element on range containing nth - if (nth < i) - { - // nth_element(first, nth, i, comp); - last = i; - } - else - { - // nth_element(i+1, nth, last, comp); - first = ++i; - } - } + return median_of_ninthers_select(first, first + nth_pos, last, + std::move(compare), std::move(projection)); } //////////////////////////////////////////////////////////// // Generic nth_element overload, slightly modified compared // to the standard library one to avoid recomputing sizes // over and over again, which might be too expensive for - // forward and bidirectional iterators + // forward and bidirectional iterators - also returns the + // nth iterator template auto nth_element(ForwardIterator first, ForwardIterator last, @@ -298,9 +64,8 @@ namespace detail -> ForwardIterator { using category = iterator_category_t; - return detail::nth_element(first, last, nth_pos, size, - std::move(compare), std::move(projection), - category{}); + return detail::nth_element(category{}, first, last, nth_pos, size, + std::move(compare), std::move(projection)); } }} From 64a140d5bb334fc42b87a19d0b09c4391ca76016 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 25 Jan 2021 11:17:23 +0100 Subject: [PATCH 77/90] Categorize swap_ranges invocations (#180) Split swap_ranges calls into swap_ranges_inner and swap_ranges_overlap, where the first has the "no overlap" guarantee and checks to ensure that said guarantee holds. This commit also adds an audit mechanism to cpp-sort for checks that are too expensive for CPPSORT_ASSERT, and starts using it in swap_ranges_inner to ensure that the "no overlap" guarantee holds. --- include/cpp-sort/detail/block_sort.h | 14 ++-- include/cpp-sort/detail/config.h | 18 ++++- include/cpp-sort/detail/grail_sort.h | 12 +-- .../cpp-sort/detail/merge_insertion_sort.h | 4 +- include/cpp-sort/detail/quick_merge_sort.h | 6 +- include/cpp-sort/detail/rotate.h | 4 +- include/cpp-sort/detail/swap_ranges.h | 76 ++++++++++++++++++- tools/test_failing_sorter.cpp | 1 + 8 files changed, 110 insertions(+), 25 deletions(-) diff --git a/include/cpp-sort/detail/block_sort.h b/include/cpp-sort/detail/block_sort.h index 291e2844..74f15646 100644 --- a/include/cpp-sort/detail/block_sort.h +++ b/include/cpp-sort/detail/block_sort.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2020 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ @@ -206,7 +206,7 @@ namespace detail } // BlockSwap - detail::swap_ranges(std::move(A_index), std::move(A_last), std::move(insert_index)); + detail::swap_ranges_overlap(std::move(A_index), std::move(A_last), std::move(insert_index)); } // merge operation without a buffer @@ -765,7 +765,7 @@ namespace detail if (cache_size > 0 && lastA.length() <= cache_size) { detail::move(lastA.start, lastA.end, cache.begin()); } else if (buffer2.length() > 0) { - detail::swap_ranges(lastA.start, lastA.end, buffer2.start); + detail::swap_ranges_overlap(lastA.start, lastA.end, buffer2.start); } if (blockA.length() > 0) { @@ -786,7 +786,7 @@ namespace detail minA = findA; } } - detail::swap_ranges(blockA.start, blockA.start + block_size, minA); + detail::swap_ranges_overlap(blockA.start, blockA.start + block_size, minA); // swap the first item of the previous A block back with its original value, which is stored in buffer1 iter_swap(blockA.start, indexA); @@ -815,8 +815,8 @@ namespace detail detail::move(blockA.start, blockA.start + block_size, cache.begin()); detail::move(B_split, B_split + B_remaining, blockA.start + (block_size - B_remaining)); } else { - detail::swap_ranges(blockA.start, blockA.start + block_size, buffer2.start); - detail::swap_ranges(B_split, B_split + B_remaining, blockA.start + (block_size - B_remaining)); + detail::swap_ranges_overlap(blockA.start, blockA.start + block_size, buffer2.start); + detail::swap_ranges_overlap(B_split, B_split + B_remaining, blockA.start + (block_size - B_remaining)); } } else { // we are unable to use the 'buffer2' trick to speed up the rotation operation since buffer2 doesn't exist, so perform a normal rotation @@ -841,7 +841,7 @@ namespace detail blockB.end = blockB.start; } else { // roll the leftmost A block to the end by swapping it with the next B block - detail::swap_ranges(blockA.start, blockA.start + block_size, blockB.start); + detail::swap_ranges_overlap(blockA.start, blockA.start + block_size, blockB.start); lastB = { blockA.start, blockA.start + block_size }; blockA.start += block_size; diff --git a/include/cpp-sort/detail/config.h b/include/cpp-sort/detail/config.h index 414de887..72d01d48 100644 --- a/include/cpp-sort/detail/config.h +++ b/include/cpp-sort/detail/config.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2020 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_CONFIG_H_ @@ -100,6 +100,22 @@ # endif #endif +//////////////////////////////////////////////////////////// +// CPPSORT_AUDIT + +// Some debug checks might be way too expensive for most +// scenarios, but still of great help when debugging tough +// problems, hence this audit feature + +#ifndef CPPSORT_AUDIT +# ifdef CPPSORT_ENABLE_AUDITS +# include +# define CPPSORT_AUDIT(...) assert((__VA_ARGS__)) +# else +# define CPPSORT_AUDIT(...) +# endif +#endif + //////////////////////////////////////////////////////////// // CPPSORT_DEPRECATED diff --git a/include/cpp-sort/detail/grail_sort.h b/include/cpp-sort/detail/grail_sort.h index 89be2579..1c1bd0c6 100644 --- a/include/cpp-sort/detail/grail_sort.h +++ b/include/cpp-sort/detail/grail_sort.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2020 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ @@ -131,7 +131,7 @@ namespace grail ++M; } if (M != left_it) { - detail::swap_ranges(M, M + (middle - left_it), left_it); + detail::swap_ranges_overlap(M, M + (middle - left_it), left_it); } } @@ -389,7 +389,7 @@ namespace grail int fnext = compare(proj(keys[cidx]), midkey_proj) < 0 ? 0 : 1; if(fnext == frest) { if (havebuf) { - detail::swap_ranges(prest-lblock, prest-(lblock-lrest), prest); + detail::swap_ranges_overlap(prest-lblock, prest-(lblock-lrest), prest); } prest = pidx; lrest = lblock; @@ -411,7 +411,7 @@ namespace grail if (llast) { if (frest) { if (havebuf) { - detail::swap_ranges(prest-lblock, prest-(lblock-lrest), prest); + detail::swap_ranges_overlap(prest-lblock, prest-(lblock-lrest), prest); } prest = pidx; lrest = lblock * nblock2; @@ -427,7 +427,7 @@ namespace grail } } else { if (havebuf) { - detail::swap_ranges(prest, prest+lrest, prest-lblock); + detail::swap_ranges_overlap(prest, prest+lrest, prest-lblock); } } } @@ -570,7 +570,7 @@ namespace grail } } if (p != u - 1) { - detail::swap_ranges(arr1+(u-1)*lblock, arr1+u*lblock, arr1+p*lblock); + detail::swap_ranges_overlap(arr1+(u-1)*lblock, arr1+u*lblock, arr1+p*lblock); iter_swap(keys+(u-1), keys+p); if (midkey == u - 1 || midkey == p) { midkey ^= (u - 1) ^ p; diff --git a/include/cpp-sort/detail/merge_insertion_sort.h b/include/cpp-sort/detail/merge_insertion_sort.h index 58f738b8..b4336621 100644 --- a/include/cpp-sort/detail/merge_insertion_sort.h +++ b/include/cpp-sort/detail/merge_insertion_sort.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2020 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_MERGE_INSERTION_SORT_H_ @@ -231,7 +231,7 @@ namespace detail auto iter_swap(group_iterator lhs, group_iterator rhs) -> void { - detail::swap_ranges(lhs.base(), lhs.base() + lhs.size(), rhs.base()); + detail::swap_ranges_inner(lhs.base(), lhs.base() + lhs.size(), rhs.base()); } //////////////////////////////////////////////////////////// diff --git a/include/cpp-sort/detail/quick_merge_sort.h b/include/cpp-sort/detail/quick_merge_sort.h index a223e042..e5d1fdf9 100644 --- a/include/cpp-sort/detail/quick_merge_sort.h +++ b/include/cpp-sort/detail/quick_merge_sort.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2020 Morwenn + * Copyright (c) 2018-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_QUICK_MERGE_SORT_H_ @@ -54,7 +54,7 @@ namespace detail for (; first1 != last1; ++result) { if (first2 == last2) { - detail::swap_ranges(first1, last1, result); + detail::swap_ranges_inner(first1, last1, result); return; } @@ -77,7 +77,7 @@ namespace detail Compare compare, Projection projection) -> void { - auto buffer_end = detail::swap_ranges(first, middle, buffer); + auto buffer_end = detail::swap_ranges_inner(first, middle, buffer); internal_half_inplace_merge(buffer, buffer_end, middle, last, first, size_left, std::move(compare), std::move(projection)); } diff --git a/include/cpp-sort/detail/rotate.h b/include/cpp-sort/detail/rotate.h index 6cd3c5ad..6803873b 100644 --- a/include/cpp-sort/detail/rotate.h +++ b/include/cpp-sort/detail/rotate.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ @@ -116,7 +116,7 @@ namespace detail const difference_type m2 = last - middle; if (m1 == m2) { - detail::swap_ranges(first, middle, middle); + detail::swap_ranges_inner(first, middle, middle); return middle; } const difference_type g = gcd(m1, m2); diff --git a/include/cpp-sort/detail/swap_ranges.h b/include/cpp-sort/detail/swap_ranges.h index 82f0e15b..abbee439 100644 --- a/include/cpp-sort/detail/swap_ranges.h +++ b/include/cpp-sort/detail/swap_ranges.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_SWAP_RANGES_H_ @@ -8,18 +8,32 @@ //////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// +#include +#include +#include #include +#include "config.h" +#include "move.h" +#include "type_traits.h" + +#include namespace cppsort { namespace detail { + //////////////////////////////////////////////////////////// + // swap_ranges_overlap + // + // Most basic swap_ranges implementation, somehow tolerates + // overlapping ranges - at least enough to get some of the + // library's algorithms to work + template - auto swap_ranges(ForwardIterator1 first1, ForwardIterator1 last1, ForwardIterator2 first2) + auto swap_ranges_overlap(ForwardIterator1 first1, ForwardIterator1 last1, ForwardIterator2 first2) -> ForwardIterator2 { - while (first1 != last1) - { + while (first1 != last1) { using utility::iter_swap; iter_swap(first1, first2); @@ -28,6 +42,60 @@ namespace detail } return first2; } + + //////////////////////////////////////////////////////////// + // swap_ranges_inner + // + // swap_ranges flavor to use when two ranges belong to the + // same collection but can not overlap, might also provide + // additional diagnostics when the precondition is violated + + template + auto swap_ranges_inner_impl(std::forward_iterator_tag, + ForwardIterator first1, ForwardIterator last1, + ForwardIterator first2) + -> ForwardIterator + { +#ifdef CPPSORT_ENABLE_AUDITS + bool ranges_overlap = false; + // This check assumes that first1 <= last1 + auto last2 = std::next(first2, std::distance(first1, last1)); + for (auto it = first1 ; it != last1 ; ++it) { + if (it == first2) { + ranges_overlap = true; + } + if (it == last2 && it != first1) { + ranges_overlap = true; + } + } + CPPSORT_AUDIT(not ranges_overlap); +#endif + return swap_ranges_overlap(first1, last1, first2); + } + + template + auto swap_ranges_inner_impl(std::random_access_iterator_tag, + RandomAccessIterator first1, RandomAccessIterator last1, + RandomAccessIterator first2) + -> RandomAccessIterator + { + CPPSORT_ASSERT(first1 <= last1); + + auto last2 = first2 + (last1 - first1); + (void)last2; + CPPSORT_ASSERT(not (first2 >= first1 && first2 < last1)); + CPPSORT_ASSERT(not (last2 > first1 && last2 <= last1)); + + return detail::swap_ranges_overlap(first1, last1, first2); + } + + template + auto swap_ranges_inner(ForwardIterator first1, ForwardIterator last1, ForwardIterator first2) + -> ForwardIterator + { + using category = iterator_category_t; + return swap_ranges_inner_impl(category{}, first1, last1, first2); + } }} #endif // CPPSORT_DETAIL_SWAP_RANGES_H_ diff --git a/tools/test_failing_sorter.cpp b/tools/test_failing_sorter.cpp index 98ea2616..42418799 100644 --- a/tools/test_failing_sorter.cpp +++ b/tools/test_failing_sorter.cpp @@ -11,6 +11,7 @@ #include #define CPPSORT_ENABLE_ASSERTIONS +//#define CPPSORT_ENABLE_AUDITS #include #include From dc15d5b02e597c882fa9388821cf900651a6c819 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 25 Jan 2021 12:13:09 +0100 Subject: [PATCH 78/90] Document CPPSORT_ENABLE_AUDITS --- docs/Home.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/Home.md b/docs/Home.md index b9102cae..97402705 100644 --- a/docs/Home.md +++ b/docs/Home.md @@ -67,12 +67,16 @@ Some old components undergo deprecation before being removed in the following ma *New in version 1.8.0* -### Assertions +### Assertions & audits Some algorithms have assertions to guard against accidental logic issues (mostly in algorithms adapted from other projects), but they are disabled by default. You can enable these assertions by defining the preprocessor macro `CPPSORT_ENABLE_ASSERTIONS`. This new macro still honours `NDEBUG`, so assertions won't be enabled anyway if `NDEBUG` is defined. +A similar `CPPSORT_ENABLE_AUDITS` macro can be defined to enable audits: those are expensive assertions which are not enabled by `CPPSORT_ENABLE_ASSERTIONS` because they are too expensive, to the point that they might even change the complexity of some algorithms. + *New in version 1.6.0* +*New in version 1.9.0*: `CPPSORT_ENABLE_AUDITS` + ## Miscellaneous This wiki also includes a small section about the [[original research|Original research]] that happened during the conception of the library and the results of this research. While it is not needed to understand how the library works or how to use it, it may be of interest if you want to discover new things about sorting. From 17f8f002a0cd71b318cded45917fa589c6d93f2e Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 25 Jan 2021 17:05:35 +0100 Subject: [PATCH 79/90] Make projection_compare public This includes the following changes: - Move it out of detail and in cpp-sort/comparators - Get rid of its unused getters - Make it conditionally branchless (#177) - Make it a transparent comparator - Document it This commit includes a little cleanup of the corresponding documentation page. --- docs/Comparators.md | 37 ++++++-- .../adapters/container_aware_adapter.h | 24 +++--- .../cpp-sort/comparators/projection_compare.h | 85 +++++++++++++++++++ .../detail/container_aware/merge_sort.h | 4 +- include/cpp-sort/detail/projection_compare.h | 67 --------------- include/cpp-sort/sorter_facade.h | 52 ++++++------ 6 files changed, 154 insertions(+), 115 deletions(-) create mode 100644 include/cpp-sort/comparators/projection_compare.h delete mode 100644 include/cpp-sort/detail/projection_compare.h diff --git a/docs/Comparators.md b/docs/Comparators.md index 47b4c4c1..0139fb1a 100644 --- a/docs/Comparators.md +++ b/docs/Comparators.md @@ -1,9 +1,29 @@ -While comparators are not inherent to sorting *per se*, most sorting algorithms (at least in this library) are *comparison sorts*, hence providing some common useful comparators along with the sorters can be useful. Every comparator in this module satisfies the [`BinaryPredicate`](https://en.cppreference.com/w/cpp/concept/BinaryPredicate) library concept. +While comparators are not inherent to sorting *per se*, most sorting algorithms (at least in this library) are *comparison sorts*, hence providing some common useful comparators along with the sorters can be useful. Every comparator in this module satisfies the [`BinaryPredicate`][binary-predicate] library concept. Every non-refined comparator described below is also a [transparent comparator][transparent-comparator]. While this ability is not used by the library itself, it means that the comparators can be used with the standard library associative containers to compare heterogeneous objects without having to create temporaries. *Changed in version 1.5.0:* every non-refined comparator is now a [transparent comparator][transparent-comparator]. +### `projection_compare` + +```cpp +#include +``` + +The class template `projection_compare` can be used to embed a comparison and a projection in a single comparison object, allowing to provide projection support to algorithms that only support comparisons, such as standard library algorithms prior to C++20. Both the passed comparison and projection functions can be [*Callable*][callable]. + +It is accompanied by a `make_projection_compare` function template to avoid having to pass the template parameters by hand. + +**Example:** + +```cpp +// Sort a family from older to younger member +std::vector family = { /* ... */ }; +std::sort(family.begin(), family.end(), cppsort::make_projection_compare(std::greater<>{}, &Person::age)); +``` + +*New in version 1.9.0* + ### Total order comparators ```cpp @@ -25,7 +45,7 @@ The comparators `total_less` and `total_order` are [customization points][custom That said, the comparators are currently unable to discriminate between quiet and signaling NaNs, so they compare equivalent. When it doesn't handle a type natively and ADL doesn't find any suitable `total_less` function in a class namespace, `cppsort::total_less` does *not* fall back to `operator<`; see [P0100][P0100] for the rationale (it applies to the whole `total_*` family of customization points). -Total order comparators are considered as [generating branchless code](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#branchless-traits) when comparing instances of a type that satisfies [`std::is_integral`](https://en.cppreference.com/w/cpp/types/is_integral). +Total order comparators are considered as [generating branchless code][branchless-traits] when comparing instances of a type that satisfies [`std::is_integral`][std-is-integral]. *Changed in version 1.5.0:* `total_greater` and `total_less` are respectively of type `total_greater_t` and `total_less_t`. @@ -47,7 +67,7 @@ The comparators `weak_less` and `weak_order` are [customization points][custom-p When it doesn't handle a type natively and ADL doesn't find any suitable `weak_less` function in a class namespace, `cppsort::weak_less` falls back to `cppsort::total_less` since a total order is also a weak order (it applies to the whole `weak_*` family of customization points). -Weak order comparators are considered as [generating branchless code](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#branchless-traits) when comparing instances of a type that satisfies [`std::is_integral`](https://en.cppreference.com/w/cpp/types/is_integral). +Weak order comparators are considered as [generating branchless code][branchless-traits] when comparing instances of a type that satisfies [`std::is_integral`][std-is-integral]. *Changed in version 1.5.0:* `weak_greater` and `weak_less` are respectively of type `weak_greater_t` and `weak_less_t`. @@ -62,7 +82,7 @@ The comparators `partial_less` and `partial_order` are [customization points][cu When it doesn't handle a type natively and ADL doesn't find any suitable `partial_less` function in a class namespace, `cppsort::partial_less` falls back to `cppsort::weak_less` since a weak order is also a partial order (it applies to the whole `partial_*` family of customization points). -Partial order comparators are considered as [generating branchless code](https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#branchless-traits) when comparing instances of a type that satisfies [`std::is_arithmetic`](https://en.cppreference.com/w/cpp/types/is_arithmetic). +Partial order comparators are considered as [generating branchless code][branchless-traits] when comparing instances of a type that satisfies [`std::is_arithmetic`][std-is-arithmetic]. *Changed in version 1.5.0:* `partial_greater` and `partial_less` are respectively of type `partial_greater_t` and `partial_less_t`. @@ -72,7 +92,7 @@ Partial order comparators are considered as [generating branchless code](https:/ #include ``` -The comparator `natural_less` is a [customization point][custom-point] that can be used to perform a [natural sort][natural-sort]. The function handles any two forward iterable sequences of `char` out of the box using [`std::isdigit`][is-digit] to identify digits (which includes `std::string`, `std::vector` and `char[]`). Other character types and locales are currently not handled and it is unlikely that the library will evolve more than switching to ``'s `std::isdigit` instead of ``'s one. +The comparator `natural_less` is a [customization point][custom-point] that can be used to perform a [natural sort][natural-sort]. The function handles any two forward iterable sequences of `char` out of the box using [`std::isdigit`][std-is-digit] to identify digits (which includes `std::string`, `std::vector` and `char[]`). Other character types and locales are currently not handled and it is unlikely that the library will evolve more than switching to ``'s `std::isdigit` instead of ``'s one. *Changed in version 1.5.0:* `natural_less` can compare heterogeneous types as long as they provide `begin` and `end` functions returning iterators to a sequence of `char`. @@ -111,14 +131,19 @@ The two-parameter version of the customization point calls the three-parameter o *Changed in version 1.5.0:* `case_insensitive_less` is an instance of type `case_insensitive_less_t`. + [binary-predicate]: https://en.cppreference.com/w/cpp/concept/BinaryPredicate + [branchless-traits]https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#branchless-traits + [callable]: https://en.cppreference.com/w/cpp/named_req/Callable [case-sensitivity]: https://en.wikipedia.org/wiki/Case_sensitivity [cppcon2015-compare]: https://github.com/CppCon/CppCon2015/tree/master/Presentations/Comparison%20is%20not%20simple%2C%20but%20it%20can%20be%20simpler%20-%20Lawrence%20Crowl%20-%20CppCon%202015 [custom-point]: https://ericniebler.com/2014/10/21/customization-point-design-in-c11-and-beyond/ - [is-digit]: https://en.cppreference.com/w/cpp/string/byte/isdigit [natural-sort]: https://en.wikipedia.org/wiki/Natural_sort_order [P0100]: http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/p0100r1.html [partial-order]: https://en.wikipedia.org/wiki/Partially_ordered_set#Formal_definition [refining]: https://github.com/Morwenn/cpp-sort/wiki/Refined-functions + [std-is-arithmetic]: https://en.cppreference.com/w/cpp/types/is_arithmetic + [std-is-digit]: https://en.cppreference.com/w/cpp/string/byte/isdigit + [std-is-integral]: https://en.cppreference.com/w/cpp/types/is_integral [std-locale]: https://en.cppreference.com/w/cpp/locale/locale [to-lower]: https://en.cppreference.com/w/cpp/locale/ctype/tolower [total-order]: https://en.wikipedia.org/wiki/Total_order diff --git a/include/cpp-sort/adapters/container_aware_adapter.h b/include/cpp-sort/adapters/container_aware_adapter.h index 4d9adcc4..a3bf4939 100644 --- a/include/cpp-sort/adapters/container_aware_adapter.h +++ b/include/cpp-sort/adapters/container_aware_adapter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2020 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_ADAPTERS_CONTAINER_AWARE_ADAPTER_H_ @@ -11,10 +11,10 @@ #include #include #include +#include #include #include #include -#include "../detail/projection_compare.h" #include "../detail/type_traits.h" namespace cppsort @@ -221,20 +221,18 @@ namespace cppsort detail::can_comparison_sort< Sorter, Iterable, - detail::projection_compare, Projection> + projection_compare, Projection> >::value, conditional_t< Stability, std::false_type, decltype(detail::adl_despair{}(this->get(), iterable, - detail::make_projection_compare(std::less<>{}, - std::move(projection)))) + make_projection_compare(std::less<>{}, std::move(projection)))) > > { return detail::adl_despair{}(this->get(), iterable, - detail::make_projection_compare(std::less<>{}, - std::move(projection))); + make_projection_compare(std::less<>{}, std::move(projection))); } template< @@ -250,7 +248,7 @@ namespace cppsort not detail::can_comparison_sort< Sorter, Iterable, - detail::projection_compare, Projection> + projection_compare, Projection> >::value, conditional_t< Stability, @@ -295,20 +293,18 @@ namespace cppsort detail::can_comparison_sort< Sorter, Iterable, - detail::projection_compare + projection_compare >::value, conditional_t< Stability, std::false_type, decltype(detail::adl_despair{}(this->get(), iterable, - detail::make_projection_compare(std::move(compare), - std::move(projection)))) + make_projection_compare(std::move(compare), std::move(projection)))) > > { return detail::adl_despair{}(this->get(), iterable, - detail::make_projection_compare(std::move(compare), - std::move(projection))); + make_projection_compare(std::move(compare), std::move(projection))); } template< @@ -323,7 +319,7 @@ namespace cppsort not detail::can_comparison_sort< Sorter, Iterable, - detail::projection_compare + projection_compare >::value, conditional_t< Stability, diff --git a/include/cpp-sort/comparators/projection_compare.h b/include/cpp-sort/comparators/projection_compare.h new file mode 100644 index 00000000..e2e6fdde --- /dev/null +++ b/include/cpp-sort/comparators/projection_compare.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2016-2021 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_COMPARATORS_PROJECTION_COMPARE_H_ +#define CPPSORT_COMPARATORS_PROJECTION_COMPARE_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include "../detail/type_traits.h" + +namespace cppsort +{ + template + class projection_compare + { + private: + + using compare_t = detail::remove_cvref_t< + decltype(utility::as_function(std::declval())) + >; + using projection_t = detail::remove_cvref_t< + decltype(utility::as_function(std::declval())) + >; + std::tuple data; + + public: + + projection_compare(Compare compare, Projection projection): + data(utility::as_function(compare), utility::as_function(projection)) + {} + + template + constexpr auto operator()(T&& lhs, U&& rhs) + noexcept(noexcept(std::get<0>(data)(std::get<1>(data)(std::forward(lhs)), + std::get<1>(data)(std::forward(rhs))))) + -> decltype(std::get<0>(data)(std::get<1>(data)(std::forward(lhs)), + std::get<1>(data)(std::forward(rhs)))) + { + return std::get<0>(data)(std::get<1>(data)(std::forward(lhs)), + std::get<1>(data)(std::forward(rhs))); + } + + template + constexpr auto operator()(T&& lhs, U&& rhs) const + noexcept(noexcept(std::get<0>(data)(std::get<1>(data)(std::forward(lhs)), + std::get<1>(data)(std::forward(rhs))))) + -> decltype(std::get<0>(data)(std::get<1>(data)(std::forward(lhs)), + std::get<1>(data)(std::forward(rhs)))) + { + return std::get<0>(data)(std::get<1>(data)(std::forward(lhs)), + std::get<1>(data)(std::forward(rhs))); + } + + using is_transparent = void; + }; + + template + auto make_projection_compare(Compare compare, Projection projection) + -> projection_compare + { + return { std::move(compare), std::move(projection) }; + } + + namespace utility + { + template + struct is_probably_branchless_comparison, T>: + cppsort::detail::conjunction< + is_probably_branchless_projection, + is_probably_branchless_comparison< + Compare, + cppsort::detail::invoke_result_t + > + > + {}; + } +} + +#endif // CPPSORT_COMPARATORS_PROJECTION_COMPARE_H_ diff --git a/include/cpp-sort/detail/container_aware/merge_sort.h b/include/cpp-sort/detail/container_aware/merge_sort.h index 99d77fce..94836f8d 100644 --- a/include/cpp-sort/detail/container_aware/merge_sort.h +++ b/include/cpp-sort/detail/container_aware/merge_sort.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2020 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_CONTAINER_AWARE_MERGE_SORT_H_ @@ -14,12 +14,12 @@ #include #include #include +#include #include #include #include #include #include -#include "../projection_compare.h" namespace cppsort { diff --git a/include/cpp-sort/detail/projection_compare.h b/include/cpp-sort/detail/projection_compare.h deleted file mode 100644 index d4df7062..00000000 --- a/include/cpp-sort/detail/projection_compare.h +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) 2016-2020 Morwenn - * SPDX-License-Identifier: MIT - */ -#ifndef CPPSORT_DETAIL_PROJECTION_COMPARE_H_ -#define CPPSORT_DETAIL_PROJECTION_COMPARE_H_ - -//////////////////////////////////////////////////////////// -// Headers -//////////////////////////////////////////////////////////// -#include -#include -#include -#include "type_traits.h" - -namespace cppsort -{ -namespace detail -{ - template - class projection_compare - { - private: - - using compare_t = remove_cvref_t()))>; - using projection_t = remove_cvref_t()))>; - std::tuple data; - - public: - - projection_compare(Compare compare, Projection projection): - data(utility::as_function(compare), utility::as_function(projection)) - {} - - auto compare() const - -> compare_t - { - return std::get<0>(data); - } - - auto projection() const - -> projection_t - { - return std::get<1>(data); - } - - template - auto operator()(T&& lhs, U&& rhs) - noexcept(noexcept(std::get<0>(data)(std::get<1>(data)(std::forward(lhs)), - std::get<1>(data)(std::forward(rhs))))) - -> decltype(std::get<0>(data)(std::get<1>(data)(std::forward(lhs)), - std::get<1>(data)(std::forward(rhs)))) - { - return std::get<0>(data)(std::get<1>(data)(std::forward(lhs)), - std::get<1>(data)(std::forward(rhs))); - } - }; - - template - auto make_projection_compare(Compare compare, Projection projection) - -> projection_compare - { - return { compare, projection }; - } -}} - -#endif // CPPSORT_DETAIL_PROJECTION_COMPARE_H_ diff --git a/include/cpp-sort/sorter_facade.h b/include/cpp-sort/sorter_facade.h index 36e6d1bc..8da56705 100644 --- a/include/cpp-sort/sorter_facade.h +++ b/include/cpp-sort/sorter_facade.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2020 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORTER_FACADE_H_ @@ -11,11 +11,11 @@ #include #include #include +#include #include #include #include #include "detail/config.h" -#include "detail/projection_compare.h" #include "detail/type_traits.h" namespace cppsort @@ -1023,16 +1023,16 @@ namespace cppsort detail::has_comparison_sort_iterator< Sorter, Iterator, - detail::projection_compare, refined_t> + projection_compare, refined_t> >::value, decltype(Sorter::operator()(first, last, - detail::make_projection_compare(std::less<>{}, - refined(std::move(projection))))) + make_projection_compare(std::less<>{}, + refined(std::move(projection))))) > { return Sorter::operator()(first, last, - detail::make_projection_compare(std::less<>{}, - refined(std::move(projection)))); + make_projection_compare(std::less<>{}, + refined(std::move(projection)))); } template @@ -1048,17 +1048,17 @@ namespace cppsort detail::has_comparison_sort_iterator< Sorter, Iterator, - detail::projection_compare< + projection_compare< refined_t, refined_t > >::value, - decltype(Sorter::operator()(first, last, detail::make_projection_compare( + decltype(Sorter::operator()(first, last, make_projection_compare( refined(std::move(compare)), refined(std::move(projection))))) > { - return Sorter::operator()(first, last, detail::make_projection_compare( + return Sorter::operator()(first, last, make_projection_compare( refined(std::move(compare)), refined(std::move(projection)))); } @@ -1080,16 +1080,16 @@ namespace cppsort detail::has_comparison_sort< Sorter, Iterable, - detail::projection_compare< + projection_compare< std::less<>, refined_t > >::value, - decltype(Sorter::operator()(std::forward(iterable), detail::make_projection_compare( + decltype(Sorter::operator()(std::forward(iterable), make_projection_compare( std::less<>{}, refined(std::move(projection))))) > { - return Sorter::operator()(std::forward(iterable), detail::make_projection_compare( + return Sorter::operator()(std::forward(iterable), make_projection_compare( std::less<>{}, refined(std::move(projection)))); } @@ -1110,7 +1110,7 @@ namespace cppsort not detail::has_comparison_sort< Sorter, Iterable, - detail::projection_compare< + projection_compare< std::less<>, refined_t > @@ -1118,19 +1118,19 @@ namespace cppsort detail::has_comparison_sort_iterator< Sorter, decltype(std::begin(iterable)), - detail::projection_compare< + projection_compare< std::less<>, refined_t > >::value, decltype(Sorter::operator()(std::begin(iterable), std::end(iterable), - detail::make_projection_compare(std::less<>{}, - refined(std::move(projection))))) + make_projection_compare(std::less<>{}, + refined(std::move(projection))))) > { return Sorter::operator()(std::begin(iterable), std::end(iterable), - detail::make_projection_compare(std::less<>{}, - refined(std::move(projection)))); + make_projection_compare(std::less<>{}, + refined(std::move(projection)))); } template @@ -1145,17 +1145,17 @@ namespace cppsort detail::has_comparison_sort< Sorter, Iterable, - detail::projection_compare< + projection_compare< refined_t, refined_t > >::value, - decltype(Sorter::operator()(std::forward(iterable), detail::make_projection_compare( + decltype(Sorter::operator()(std::forward(iterable), make_projection_compare( refined(std::move(compare)), refined(std::move(projection))))) > { - return Sorter::operator()(std::forward(iterable), detail::make_projection_compare( + return Sorter::operator()(std::forward(iterable), make_projection_compare( refined(std::move(compare)), refined(std::move(projection)))); } @@ -1172,7 +1172,7 @@ namespace cppsort not detail::has_comparison_sort< Sorter, Iterable, - detail::projection_compare< + projection_compare< refined_t, refined_t > @@ -1180,17 +1180,17 @@ namespace cppsort detail::has_comparison_sort_iterator< Sorter, decltype(std::begin(iterable)), - detail::projection_compare< + projection_compare< refined_t, refined_t > >::value, - decltype(Sorter::operator()(std::begin(iterable), std::end(iterable), detail::make_projection_compare( + decltype(Sorter::operator()(std::begin(iterable), std::end(iterable), make_projection_compare( refined(std::move(compare)), refined(std::move(projection))))) > { - return Sorter::operator()(std::begin(iterable), std::end(iterable), detail::make_projection_compare( + return Sorter::operator()(std::begin(iterable), std::end(iterable), make_projection_compare( refined(std::move(compare)), refined(std::move(projection)))); } From b00c645ab9996a16d0857139b6c1b7d2209489b5 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 25 Jan 2021 22:12:32 +0100 Subject: [PATCH 80/90] Revert "Document library decisions wrt LWG3031 (#136)" This reverts commit 0e8c9067b8396c629a6434920aefbac7aab089cd. --- docs/Home.md | 9 --------- 1 file changed, 9 deletions(-) diff --git a/docs/Home.md b/docs/Home.md index 97402705..e01df120 100644 --- a/docs/Home.md +++ b/docs/Home.md @@ -31,14 +31,6 @@ If the library throws any other exception, it will likely come from user code. T *New in version 1.4.0* -### Support for predicates accepting non-`const` parameters - -Following the resolution of the standard library issues [LWG3031][lwg3031], there is implementation freedom for standard library algorithms to accept predicates that take their arguments by non-`const` reference. It is the responsibility of the caller to ensure that, should the predicate accept its arguments by both `const` and non-`const` reference, the results are consistent between overloads. - -**cpp-sort** fully supports predicates that take their arguments via non-`const` reference provided that the guarantees outlined in the previous paragraph are respected. - -*New in version 1.7.0* - ## Library information & configuration ### Versioning @@ -87,5 +79,4 @@ If you ever feel that this wiki is incomplete, that it needs more examples or mo Hope you have fun! - [lwg3031]: https://wg21.link/LWG3031 [swappable]: https://en.cppreference.com/w/cpp/concepts/swappable From 803787c96d382eca1f9ccf3e8935dc6d3bb17a5d Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 25 Jan 2021 23:12:25 +0100 Subject: [PATCH 81/90] Doc: note about determinism & reproducibility --- docs/Home.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/Home.md b/docs/Home.md index e01df120..035189c4 100644 --- a/docs/Home.md +++ b/docs/Home.md @@ -31,6 +31,12 @@ If the library throws any other exception, it will likely come from user code. T *New in version 1.4.0* +### Determinism & reproducibility + +So far every algorithm in the library is deterministic: for a given input, one should always get the exact same sequence of operations performed. It was a deliberate choice not to use algorithms such as random pivot quicksort or random sampling algorithms. + +The library does not contain multithreaded algorithms either for now, further guaranteeing reproducibility. + ## Library information & configuration ### Versioning From 9beca1509479158382ff1e90f0851179e37c86d1 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 27 Jan 2021 14:01:25 +0100 Subject: [PATCH 82/90] Prefix CMake options with CPPSORT_ --- CMakeLists.txt | 12 +++++++----- docs/Tooling.md | 15 +++++++++++---- testsuite/CMakeLists.txt | 30 ++++++++++++++++-------------- 3 files changed, 34 insertions(+), 23 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e233b99..f5a66eef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2015-2020 Morwenn +# Copyright (c) 2015-2021 Morwenn # SPDX-License-Identifier: MIT cmake_minimum_required(VERSION 3.8.0) @@ -11,8 +11,10 @@ include(CMakePackageConfigHelpers) include(GNUInstallDirs) # Project options -option(BUILD_TESTING "Build the cpp-sort test suite" ON) -option(BUILD_EXAMPLES "Build the cpp-sort examples" OFF) +option(BUILD_TESTING "Build the cpp-sort test suite (deprecated, use CPPSORT_BUILD_TESTING)" ON) +option(BUILD_EXAMPLES "Build the cpp-sort examples (deprecated, use CPPSORT_BUILD_EXAMPLES)" OFF) +option(CPPSORT_BUILD_TESTING "Build the cpp-sort test suite" ${BUILD_TESTING}) +option(CPPSORT_BUILD_EXAMPLES "Build the cpp-sort examples" ${BUILD_EXAMPLES}) # Create cpp-sort library and configure it add_library(cpp-sort INTERFACE) @@ -77,12 +79,12 @@ export( # Build tests and/or examples if this is the main project if (PROJECT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) - if (BUILD_TESTING) + if (CPPSORT_BUILD_TESTING) enable_testing() add_subdirectory(testsuite) endif() - if (BUILD_EXAMPLES) + if (CPPSORT_BUILD_EXAMPLES) add_subdirectory(examples) endif() endif() diff --git a/docs/Tooling.md b/docs/Tooling.md index 9312d2bb..c5f880da 100644 --- a/docs/Tooling.md +++ b/docs/Tooling.md @@ -25,13 +25,20 @@ target_link_libraries(my-target PRIVATE cpp-sort::cpp-sort) ### Building cpp-sort The project's CMake files do offer some options, but they are mainly used to configure the test suite and the examples: -* `BUILD_TESTING`: whether to build the test suite, defaults to `ON`. -* `BUILD_EXAMPLES`: whether to build the examples, defaults to `OFF`. -* `ENABLE_COVERAGE`: whether to produce code coverage information when building the test suite, defaults to `OFF`. -* `USE_VALGRIND`: whether to run the test suite through Valgrind, defaults to `OFF`. +* `CPPSORT_BUILD_TESTING`: whether to build the test suite, defaults to `ON`. +* `CPPSORT_BUILD_EXAMPLES`: whether to build the examples, defaults to `OFF`. +* `CPPSORT_ENABLE_COVERAGE`: whether to produce code coverage information when building the test suite, defaults to `OFF`. +* `CPPSORT_USE_VALGRIND`: whether to run the test suite through Valgrind, defaults to `OFF`. +* `CPPSORT_SANITIZE`: values to pass to the `-fsanitize` falgs of compilers that supports them, default to empty. + +The same options exist without the `CPPSORT_` prefix exist, but are deprecated. For compatibility reasons, the options with the `CPPSORT_` prefix default the values of the equivalent unprefixed options. *New in version 1.6.0:* added the option `BUILD_EXAMPLES`. +*New in version 1.9.0:* options with the `CPPSORT_` prefix. + +***WARNING:** options without a `CPPSORT_` prefixed are deprecated in version 1.9.0 and removed in version 2.0.0.* + [Catch2][catch2] 2.6.0 or greater is required to build the tests: if a suitable version has been installed on the system it will be used, otherwise the latest Catch2 release will be downloaded. *Changed in version 1.7.0:* if a suitable Catch2 version is found on the system, it will be used. diff --git a/testsuite/CMakeLists.txt b/testsuite/CMakeLists.txt index 846c135b..25611b64 100644 --- a/testsuite/CMakeLists.txt +++ b/testsuite/CMakeLists.txt @@ -1,13 +1,16 @@ -# Copyright (c) 2015-2020 Morwenn +# Copyright (c) 2015-2021 Morwenn # SPDX-License-Identifier: MIT include(cpp-sort-utils) include(DownloadProject) # Test suite options -option(USE_VALGRIND "Whether to run the tests with Valgrind" OFF) -option(ENABLE_COVERAGE "Whether to produce code coverage" OFF) -set(SANITIZE "" CACHE STRING "Comma-separated list of options to pass to -fsanitize") +option(USE_VALGRIND "Whether to run the tests with Valgrind (deprecated, use CPPSORT_USE_VALGRIND)" OFF) +option(ENABLE_COVERAGE "Whether to produce code coverage (deprecated, use CPPSORT_ENABLE_COVERAGE)" OFF) +set(SANITIZE "" CACHE STRING "Comma-separated list of options to pass to -fsanitize (deprecated, use CPPSORT_SANITIZE)") +option(CPPSORT_USE_VALGRIND "Whether to run the tests with Valgrind" ${USE_VALGRIND}) +option(CPPSORT_ENABLE_COVERAGE "Whether to produce code coverage" ${ENABLE_COVERAGE}) +set(CPPSORT_SANITIZE ${SANITIZE} CACHE STRING "Comma-separated list of options to pass to -fsanitize") # Find & configure Catch2 for the tests message(STATUS "Looking for Catch2 2.6.0+") @@ -28,7 +31,7 @@ endif() include(Catch) macro(configure_tests target) - # Make testing tools easiyl available to tests + # Make testing tools easily available to tests # regardless of the directory of the test target_include_directories(${target} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) @@ -64,18 +67,18 @@ macro(configure_tests target) endif() # Optionally enable sanitizers - if (UNIX AND SANITIZE) + if (UNIX AND CPPSORT_SANITIZE) target_compile_options(${target} PRIVATE - -fsanitize=${SANITIZE} + -fsanitize=${CPPSORT_SANITIZE} -fno-sanitize-recover=all ) set_property(TARGET ${target} APPEND_STRING PROPERTY LINK_FLAGS - " -fsanitize=${SANITIZE}" + " -fsanitize=${CPPSORT_SANITIZE}" ) endif() - if (ENABLE_COVERAGE) + if (CPPSORT_ENABLE_COVERAGE) find_package(codecov) add_coverage(${target}) @@ -197,7 +200,7 @@ add_executable(main-tests ) configure_tests(main-tests) -if (NOT "${SANITIZE}" MATCHES "address|memory") +if (NOT "${CPPSORT_SANITIZE}" MATCHES "address|memory") add_executable(heap-memory-exhaustion-tests # These tests are in a separate executable because we replace # the global [de]allocation functions in order to test the @@ -212,14 +215,13 @@ if (NOT "${SANITIZE}" MATCHES "address|memory") endif() # Configure coverage -if (ENABLE_COVERAGE) - set(ENABLE_COVERAGE ON CACHE BOOL "Enable coverage build." FORCE) +if (CPPSORT_ENABLE_COVERAGE) list(APPEND LCOV_REMOVE_PATTERNS "'/usr/*'") coverage_evaluate() endif() # Configure Valgrind -if (USE_VALGRIND) +if (CPPSORT_USE_VALGRIND) find_program(MEMORYCHECK_COMMAND valgrind REQUIRED) set(MEMORYCHECK_COMMAND_OPTIONS "--leak-check=full --track-origins=yes --error-exitcode=1 --show-reachable=no") if (APPLE) @@ -231,6 +233,6 @@ include(CTest) string(RANDOM LENGTH 5 ALPHABET 0123456789 RNG_SEED) catch_discover_tests(main-tests EXTRA_ARGS --rng-seed ${RNG_SEED}) -if (NOT "${SANITIZE}" MATCHES "address|memory") +if (NOT "${CPPSORT_SANITIZE}" MATCHES "address|memory") catch_discover_tests(heap-memory-exhaustion-tests EXTRA_ARGS --rng-seed ${RNG_SEED}) endif() From 938676fe7a1812ac270ad33372a6af4f86c84b6e Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 27 Jan 2021 14:55:16 +0100 Subject: [PATCH 83/90] Use the new CMake options in the worflow files --- .github/workflows/build-macos.yml | 4 ++-- .github/workflows/build-ubuntu.yml | 6 +++--- .github/workflows/build-windows.yml | 2 +- .github/workflows/code-coverage.yml | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 3ce5223c..10a117a3 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -51,9 +51,9 @@ jobs: cmake -H${{github.event.repository.name}} -Bbuild \ -DCMAKE_CONFIGURATION_TYPES=${{matrix.config.build_type}} \ -DCMAKE_BUILD_TYPE=${{matrix.config.build_type}} \ - -DSANITIZE=${{matrix.config.sanitize}} \ + -DCPPSORT_SANITIZE=${{matrix.config.sanitize}} \ -GNinja \ - -DBUILD_EXAMPLES=ON + -DCPPSORT_BUILD_EXAMPLES=ON - name: Build the test suite shell: bash diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index efc56ee5..305d196a 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -57,10 +57,10 @@ jobs: cmake -H${{github.event.repository.name}} -Bbuild \ -DCMAKE_CONFIGURATION_TYPES=${{matrix.config.build_type}} \ -DCMAKE_BUILD_TYPE=${{matrix.config.build_type}} \ - -DSANITIZE=${{matrix.config.sanitize}} \ - -DUSE_VALGRIND=${{matrix.config.valgrind}} \ + -DCPPSORT_SANITIZE=${{matrix.config.sanitize}} \ + -DCPPSORT_USE_VALGRIND=${{matrix.config.valgrind}} \ -G"Unix Makefiles" \ - -DBUILD_EXAMPLES=ON + -DCPPSORT_BUILD_EXAMPLES=ON - name: Build the test suite shell: bash diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index b7bf14e2..f5097cfa 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -41,7 +41,7 @@ jobs: -DCMAKE_CONFIGURATION_TYPES=${{matrix.build_type}} ` -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ` -G"MinGW Makefiles" ` - -DBUILD_EXAMPLES=ON + -DCPPSORT_BUILD_EXAMPLES=ON - name: Build the test suite working-directory: ${{runner.workspace}}/build diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index aad77d66..74d7571d 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -34,7 +34,7 @@ jobs: run: > cmake -H${{github.event.repository.name}} -Bbuild -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" - -DENABLE_COVERAGE=true + -DCPPSORT_ENABLE_COVERAGE=true -G"Unix Makefiles" - name: Build with coverage From 876cae834d26fa80a7c641e2ec76cd3cd5d52173 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 27 Jan 2021 15:05:56 +0100 Subject: [PATCH 84/90] Add back line CMake line I though was useless, fix coverage --- testsuite/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/testsuite/CMakeLists.txt b/testsuite/CMakeLists.txt index 25611b64..69ec9913 100644 --- a/testsuite/CMakeLists.txt +++ b/testsuite/CMakeLists.txt @@ -12,6 +12,11 @@ option(CPPSORT_USE_VALGRIND "Whether to run the tests with Valgrind" ${USE_VALGR option(CPPSORT_ENABLE_COVERAGE "Whether to produce code coverage" ${ENABLE_COVERAGE}) set(CPPSORT_SANITIZE ${SANITIZE} CACHE STRING "Comma-separated list of options to pass to -fsanitize") +# Apparently ENABLE_COVERAGE is needed either way +if (CPPSORT_ENABLE_COVERAGE) + set(ENABLE_COVERAGE ON CACHE BOOL "Enable coverage build." FORCE) +endif() + # Find & configure Catch2 for the tests message(STATUS "Looking for Catch2 2.6.0+") find_package(Catch2 2.6.0 QUIET) From 57124c92f78cf9d184b3aff92f325a36e3dbcd7b Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 28 Jan 2021 16:10:20 +0100 Subject: [PATCH 85/90] Add 'results' directories to the ignore list --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index d82f1cc6..5c76947e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ # Usual build directory build +# Benchmark results directories +results + # Files generated by project scripts *.csv *.png From 5a6a2e63ef59d85d5ea975e9c7478dd876602749 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Fri, 29 Jan 2021 15:08:53 +0100 Subject: [PATCH 86/90] Update benchmarks for 1.9.0 release --- docs/Benchmarks.md | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/docs/Benchmarks.md b/docs/Benchmarks.md index fef47f10..9745470f 100644 --- a/docs/Benchmarks.md +++ b/docs/Benchmarks.md @@ -1,6 +1,6 @@ -*Note: this page is hardly ever updated and the graphs might not reflect the most recent algorithms or optimizations. It can be used as a quick guide but if you really need a fast algorithm for a specific use case, you better run your own benchmarks.* +*Note: this page only benchmarls sorting algorithms under specific conditions. It can be used as a quick guide but if you really need a fast algorithm for a specific use case, you better run your own benchmarks.* -*Last major update: 1.8.0 release.* +*Last meaningful update: 1.9.0 release.* Benchmarking is hard and I might not be doing it right. Moreover, benchmarking sorting algorithms highlights that the time needed to sort a collection of elements depends on several things: the type to sort, the size of the collection, the cost of comparing two values, the cost of moving an element, the patterns formed by the distribution of the values in the collection to sort, the type of the collection itself, etc. The aim of this page is to help you choose a sorting algorithm depending on your needs. You can find two main kinds of benchmarks: the ones that compare algorithms against shuffled collections of different sizes, and the ones that compare algorithms against different data patterns for a given collection size. @@ -19,7 +19,7 @@ Most sorting algorithms are designed to work with random-access iterators, so th Unstable sorts are the most common sorting algorithms, and unstable sorts on random-access iterators are generally the fastest comparison sorts. If you don't know what algorithm you know, it's probably one of these ones. ![Benchmark speed of unstable sorts with increasing size for std::vector](https://i.imgur.com/6Jfj768.png) -![Benchmark speed of unstable sorts with increasing size for std::deque](https://i.imgur.com/HUiK2jf.png) +![Benchmark speed of unstable sorts with increasing size for std::deque](https://i.imgur.com/C9GypoJ.png) The plots above show a few general tendencies: * `selection_sort` is O(n²) and doesn't scale. @@ -28,8 +28,8 @@ The plots above show a few general tendencies: The quicksort derivatives and the hybrid radix sorts are generally the fastest of the lot, yet `drop_merge_sort` seems to offer interesting speedups for `std::deque` despite not being designed to be the fastest on truly shuffled data. Part of the explanation is that it uses `pdq_sort` in a buffer underneath, which might be faster for `std::deque` than truly sorting in-place. -![Benchmark unstable sorts over different patterns for std::vector](https://i.imgur.com/LL7iCQd.png) -![Benchmark unstable sorts over different patterns for std::deque](https://i.imgur.com/4rkTNeq.png) +![Benchmark unstable sorts over different patterns for std::vector](https://i.imgur.com/te098uq.png) +![Benchmark unstable sorts over different patterns for std::deque](https://i.imgur.com/aRbP7wY.png) A few random takeways: * All the algorithms are more or less adaptive, not always for the same patterns. @@ -71,13 +71,13 @@ The analysis is pretty simple here: # Bidirectional iterables -Sorting algorithms that handle non-random-access iterators are often second class citizens, but **cpp-sort** still provides a few ones. The most interesting part is that we can see how generic sorting algorithms perform compared to algorithms such as [`std::list::sort`](https://en.cppreference.com/w/cpp/container/list/sort) which are aware of the data structure they are sorting. +Sorting algorithms that handle non-random-access iterators are often second class citizens, but **cpp-sort** still provides a few ones. The most interesting part is that we can see how generic sorting algorithms perform compared to algorithms such as [`std::list::sort`][std-list-sort] which are aware of the data structure they are sorting. ![Benchmark speed of sorts with increasing size for std::list](https://i.imgur.com/Z2BDhpz.png) For elements as small as `double`, there are two clear winners here: `drop_merge_sort` and `out_of_place_adapter(pdq_sort)`. Both have in common the fact that they move a part of the collection (or the whole collection) to a contiguous memory buffer and sort it there using `pdq_sort`. The only difference is that `drop_merge_sort` does that "accidentally" while `out_of_place_adapter` was specifically introduced to sort into a contiguous memory buffer and move back for speed. -![Benchmark sorts over different patterns for std::list](https://i.imgur.com/6EftqN7.png) +![Benchmark sorts over different patterns for std::list](https://i.imgur.com/RcmJ8gL.png) `out_of_place_adapter(pdq_sort)` was not included in this benchmark, because it adapts to patterns the same way `pdq_sort` does. Comments can be added for these results: * `std::list::sort` would require elements more expensive to move for node relinking to be faster than move-based algorithms. @@ -93,7 +93,7 @@ Even fewer sorters can handle forward iterators. `out_of_place_adapter(pdq_sort) ![Benchmark sorts over different patterns for std::forward_list](https://i.imgur.com/bWZRega.png) The results are roughly the same than with bidirectional iterables: -* `std::forward_list::sort` doesn't scale well unless moves are expensive. +* [`std::forward_list::sort`][std-forward-list-sort] doesn't scale well unless moves are expensive. * Sorting out-of-place is faster than anything else. * If no extra heap memory is available, `quick_merge_sort` is the only O(n log n) algorithm that can be used, and does a fine job despite never being excellent. @@ -114,7 +114,7 @@ Integer sorting is a rather specific scenario for which many solutions exist: co ## *Inv*-adaptive algorithms -Some sorting algorithms are specifically designed to be fast when there are only a few inversions in the collection, they are known as *Inv*-adaptive algorithms since the amount of work they perform is dependent on the result of the measure of presortedness *Inv(X)*. There are two such algorithms in **cpp-sort**: `drop_merge_sort` and `split_sort` (which probably makes them *Rem*-adaptive too). Both work by removing elements from the collections to leave a *longest ascending subsequence*, sorting the removed elements and merging the two sorted sequences back into one. +Some sorting algorithms are specifically designed to be fast when there are only a few inversions in the collection, they are known as *Inv*-adaptive algorithms since the amount of work they perform is dependent on the result of the measure of presortedness *Inv(X)*. There are two such algorithms in **cpp-sort**: `drop_merge_sort` and `split_sort`. Both work by removing elements from the collections to leave a *longest ascending subsequence*, sorting the removed elements and merging the two sorted sequences back into one (which probably makes them *Rem*-adaptive too). The following plot shows how fast those algorithms are depending on the percentage of inversions in the collection to sort. They are benchmarked against `pdq_sort` because it is the algorithm they use internally to sort the remaining unsorted elements prior to the merge, which makes it easy to compare the gains and overheads of those algorithms compared to a raw `pdq_sort`. @@ -149,10 +149,15 @@ The spikes in the otherwise smooth sorting networks curve when sorting arrays of # Measures of presortedness -This benchmark for [measures of presortedness](https://github.com/Morwenn/cpp-sort/wiki/Measures-of-presortedness) is small and only intends to show the cost that these tools might incur. It is not meant to be exhaustive in any way. +This benchmark for [measures of presortedness][measures-of-presortedness] is small and only intends to show the cost that these tools might incur. It is not meant to be exhaustive in any way. ![Benchmark speed of measures of presortedness for increasing size for std::vector](https://i.imgur.com/5XniqE1.png) While the graph reasonably shows the relative cost of the different measures of presortedness, there are a few hidden traps: * *Par(X)* seems to beat every other measure, but it is a highly adaptative O(n² log n) algorithm, whose theoretical worst case might be the worst of all measures of presortedness. * *Dis(X)* looks like a O(n) algorithm in this graph, but it is actually a O(n²) algorithm with extremely efficient short-circuits in most cases. Its worst case would put it closer from *Osc(X)*. + + + [measures-of-presortedness]: https://github.com/Morwenn/cpp-sort/wiki/Measures-of-presortedness + [std-forward-list-sort]: https://en.cppreference.com/w/cpp/container/list/sort + [std-list-sort]: https://en.cppreference.com/w/cpp/container/list/sort From 279456c55786e309d196b1b5ffec85cf637c96b1 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Fri, 29 Jan 2021 18:34:05 +0100 Subject: [PATCH 87/90] Add benchmark for stable sorts without extra memory (#175) --- docs/Benchmarks.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/Benchmarks.md b/docs/Benchmarks.md index 9745470f..2e98c0f9 100644 --- a/docs/Benchmarks.md +++ b/docs/Benchmarks.md @@ -134,6 +134,21 @@ The following example uses a collection of `std::array` whose first The improvements are not always as clear as in this benchmark, but it shows that `indirect_adapter` might be an interesting tool to have in your sorting toolbox in such a scenario. +## Sorting stably without heap memory + +Only a few algorithms allow to sort a collection stably without using extra heap memory: `grail_sort` and `block_sort` can accept a fixed-size buffer (possibly of size 0) while `merge_sort` has a fallback algorithm when no heap memory is available. + +![Benchmark speed of stable sorts with no heap memory with increasing size for std::vector](https://i.imgur.com/1a64irX.png) +![Benchmark speed of stable sorts with no heap memory with increasing size for std::deque](https://i.imgur.com/U5uD8Er.png) +![Detail of the previous benchmark](https://i.imgur.com/owUictQ.png) + +`merge_sort` is definitely losing this benchmark. Interestingly enough `block_sort` is way better with a fixed buffer of 512 elements while it hardly affects `grail_sort` at all. For `std::deque`, `grail_sort` is almost always the fastest no matter what. + +![Benchmark stable sorts with no heap memory over different patterns for std::vector](https://i.imgur.com/74YxCLI.png) +![Benchmark stable sorts with no heap memory over different patterns for std::deque](https://i.imgur.com/jqek5Ii.png) + +Here `merge_sort` still loses the battle, but it also displays an impressive enough adaptiveness to presortedness and patterns. + ## Small array sorters Some sorting algorithms are particularly suited to sort very small collections: the ones provided by ``, but also the very simple ones such as `insertion_sort` or `selection_sort`. Most other sorting algorithms fallback to one of these when sorting a small collection. From 9c4220e805fc6a410501213bd474fe116cd93f74 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Fri, 29 Jan 2021 19:04:40 +0100 Subject: [PATCH 88/90] Patterns benchmark: consistency fixes --- benchmarks/patterns/bars.py | 4 ++-- benchmarks/patterns/bench.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/benchmarks/patterns/bars.py b/benchmarks/patterns/bars.py index e290461c..7a76a215 100644 --- a/benchmarks/patterns/bars.py +++ b/benchmarks/patterns/bars.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2015-2020 Morwenn +# Copyright (c) 2015-2021 Morwenn # SPDX-License-Identifier: MIT # Copyright (c) 2015 Orson Peters @@ -137,7 +137,7 @@ def main(): ax.autoscale_view() pyplot.ylim(pyplot.ylim()[0] + 1, pyplot.ylim()[1] - 1) - pyplot.title("Sorting a std::deque with $10^{}$ elements".format(round(math.log(size, 10)))) + pyplot.title("Sorting a std::vector with $10^{}$ elements".format(round(math.log(size, 10)))) pyplot.legend(loc="best") figure = pyplot.gcf() diff --git a/benchmarks/patterns/bench.cpp b/benchmarks/patterns/bench.cpp index dde27d68..c9e697c2 100644 --- a/benchmarks/patterns/bench.cpp +++ b/benchmarks/patterns/bench.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2020 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ @@ -40,7 +40,7 @@ #include "rdtsc.h" // Type of data to sort during the benchmark -using value_t = int; +using value_t = double; // Type of collection to sort using collection_t = std::vector; From 0a7feaf7f982a3eed083255f3c9f8c802d232153 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Fri, 29 Jan 2021 19:07:40 +0100 Subject: [PATCH 89/90] Add another bullet to the release checklist --- tools/release-checklist.md | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/release-checklist.md b/tools/release-checklist.md index baa37767..57ab4a79 100644 --- a/tools/release-checklist.md +++ b/tools/release-checklist.md @@ -21,6 +21,7 @@ development phase: - [ ] version.h - [ ] Home.md in the documentation - [ ] Tooling.md/Conan in the documentation +- [ ] Make sure 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`. From ca633a822c97ed3e1d192ce888edcf46f948f251 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Fri, 29 Jan 2021 19:30:52 +0100 Subject: [PATCH 90/90] Preparing release 1.9.0 --- CMakeLists.txt | 2 +- README.md | 4 ++-- conanfile.py | 4 ++-- docs/Tooling.md | 2 +- include/cpp-sort/version.h | 6 +++--- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f5a66eef..b000a91b 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.8.1 LANGUAGES CXX) +project(cpp-sort VERSION 1.9.0 LANGUAGES CXX) include(CMakePackageConfigHelpers) include(GNUInstallDirs) diff --git a/README.md b/README.md index 248fd261..3c941297 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -[![Latest Release](https://img.shields.io/badge/release-1.8.1-blue.svg)](https://github.com/Morwenn/cpp-sort/releases/tag/1.8.1) -[![Conan Package](https://img.shields.io/badge/conan-cpp--sort%2F1.8.1-blue.svg)](https://conan.io/center/cpp-sort?version=1.8.1) +[![Latest Release](https://img.shields.io/badge/release-1.9.0-blue.svg)](https://github.com/Morwenn/cpp-sort/releases/tag/1.9.0) +[![Conan Package](https://img.shields.io/badge/conan-cpp--sort%2F1.9.0-blue.svg)](https://conan.io/center/cpp-sort?version=1.9.0) [![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 97e52c35..09aec752 100644 --- a/conanfile.py +++ b/conanfile.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2018-2020 Morwenn +# Copyright (c) 2018-2021 Morwenn # SPDX-License-Identifier: MIT from conans import CMake, ConanFile @@ -8,7 +8,7 @@ class CppSortConan(ConanFile): name = "cpp-sort" - version = "1.8.1" + version = "1.9.0" description = "Additional sorting algorithms & related tools" topics = "conan", "cpp-sort", "sorting", "algorithms" url = "https://github.com/Morwenn/cpp-sort" diff --git a/docs/Tooling.md b/docs/Tooling.md index c5f880da..12d93cce 100644 --- a/docs/Tooling.md +++ b/docs/Tooling.md @@ -68,4 +68,4 @@ Alternatively you can find the packages on [Bintray][bintray], generated with th [catch2]: https://github.com/catchorg/Catch2 [cmake]: https://cmake.org/ [conan]: https://conan.io/ - [conan-center]: https://bintray.com/conan/conan-center \ No newline at end of file + [conan-center]: https://bintray.com/conan/conan-center diff --git a/include/cpp-sort/version.h b/include/cpp-sort/version.h index 5536b524..b71358d1 100644 --- a/include/cpp-sort/version.h +++ b/include/cpp-sort/version.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2020 Morwenn + * Copyright (c) 2018-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_VERSION_H_ @@ -8,7 +8,7 @@ // Semantic versioning macros #define CPPSORT_VERSION_MAJOR 1 -#define CPPSORT_VERSION_MINOR 8 -#define CPPSORT_VERSION_PATCH 1 +#define CPPSORT_VERSION_MINOR 9 +#define CPPSORT_VERSION_PATCH 0 #endif // CPPSORT_VERSION_H_