From 85748fc9026702f7ab87fe177731b822bee0ee20 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 27 Jul 2021 16:20:46 +0200 Subject: [PATCH 01/79] Use a O(n) algorithm for probe::par (#85) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The old algorithm was an unsightly O(n² log n) algorithm, and a really cunning on at that since it returned in the blink of an eye (generally even faster than that) for most inputs, and only hit its worst case for a few inputs. The new algorithm is a O(n) algorithm described by T. Altman and Y. Igarashi in *Roughly Sorting: Sequential And Parallel Approach*. It runs in O(n) space and doesn't run as fast for most inputs, but it at least doesn't hide as many surprises. The old algorithm is still used when not enough memory is available, to ensure that the next 1.x.y release won't be a breaking change, but it will be removed in cpp-sort 2.0.0. --- docs/Measures-of-presortedness.md | 1 + include/cpp-sort/probes/par.h | 106 ++++++++++++++++++++++++++---- testsuite/probes/par.cpp | 14 ++++ 3 files changed, 108 insertions(+), 13 deletions(-) diff --git a/docs/Measures-of-presortedness.md b/docs/Measures-of-presortedness.md index 76ff8154..fae0f553 100644 --- a/docs/Measures-of-presortedness.md +++ b/docs/Measures-of-presortedness.md @@ -227,6 +227,7 @@ The following definition is also given to determine whether a sequence is *p*-so | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | +| n | n | Random-access | | n² log n | 1 | Random-access | `max_for_size`: |*X*| - 1 when the last element of *X* is smaller than the first one. diff --git a/include/cpp-sort/probes/par.h b/include/cpp-sort/probes/par.h index dae6d1a2..f3278404 100644 --- a/include/cpp-sort/probes/par.h +++ b/include/cpp-sort/probes/par.h @@ -8,13 +8,19 @@ //////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// +#include #include +#include +#include #include +#include +#include #include #include #include #include #include "../detail/is_p_sorted.h" +#include "../detail/iterator_traits.h" namespace cppsort { @@ -22,6 +28,81 @@ namespace probe { namespace detail { + template + auto legacy_par_probe_algo(RandomAccessIterator first, RandomAccessIterator last, + cppsort::detail::difference_type_t size, + Compare compare, Projection projection) + -> ::cppsort::detail::difference_type_t + { + auto res = 0; + while (size > 0) { + auto p = res; + p += size / 2; + if (cppsort::detail::is_p_sorted(first, last, p, compare, projection)) { + size /= 2; + } else { + res = ++p; + size -= size / 2 + 1; + } + } + return res; + } + + template + auto par_probe_algo(RandomAccessIterator first, RandomAccessIterator last, + cppsort::detail::difference_type_t size, + Compare compare, Projection projection) + -> ::cppsort::detail::difference_type_t + { + using difference_type = ::cppsort::detail::difference_type_t; + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + // Algorithm described in *Roughly Sorting: Sequential and Parallel Approach* + // by T. Altman and Y. Igarashi + + if (size < 2) { + return 0; + } + + // Algorithm LR + std::vector b = { first }; + for (auto it = std::next(first) ; it != last ; ++it) { + if (comp(proj(*b.back()), proj(*it))) { + b.push_back(it); + } else { + b.push_back(b.back()); + } + } + + // Algorithm RL + std::vector c = { std::prev(last) }; + auto rfirst = std::make_reverse_iterator(last); + auto rlast = std::make_reverse_iterator(first); + for (auto it = std::next(rfirst) ; it != rlast ; ++it) { + if (comp(proj(*it), proj(*c.back()))) { + c.push_back(std::prev(it.base())); + } else { + c.push_back(c.back()); + } + } + std::reverse(c.begin(), c.end()); + + // Algorithm DM + std::vector d = {}; + difference_type i = c.size(); + for (auto j = i ; j > 0 ; --j) { + while (j <= i && i >= 1 && not comp(proj(*b[j - 1]), proj(*c[i - 1])) + && (j == 1 || not comp(proj(*c[i - 1]), proj(*b[j - 2])))) { + d.push_back(i - j); + --i; + } + } + + // Compute radius = max(dm) + return *std::max_element(d.begin(), d.end()); + } + struct par_impl { template< @@ -36,20 +117,19 @@ namespace probe Compare compare={}, Projection projection={}) const -> cppsort::detail::difference_type_t { - auto size = last - first; - - auto res = 0; - while (size > 0) { - auto p = res; - p += size / 2; - if (cppsort::detail::is_p_sorted(first, last, p, compare, projection)) { - size /= 2; - } else { - res = ++p; - size -= size / 2 + 1; - } + try { + return par_probe_algo(first, last, last - first, + compare, projection); + } + catch (std::bad_alloc&) { + // Old O(n^2 log n) algorithm, kept to avoid a breaking + // when no extra memory is available, might be removed + // in the future + return legacy_par_probe_algo( + first, last, last - first, + std::move(compare), std::move(projection) + ); } - return res; } template diff --git a/testsuite/probes/par.cpp b/testsuite/probes/par.cpp index 6198df2c..15611c0b 100644 --- a/testsuite/probes/par.cpp +++ b/testsuite/probes/par.cpp @@ -21,6 +21,20 @@ TEST_CASE( "presortedness measure: par", "[probe][par]" ) CHECK( par(tricky, &internal_compare::compare_to) == 7 ); } + SECTION( "roughly sorting test" ) + { + // Example from *Roughly Sorting: Sequential and Parallel Approach* + // by T. Altman and Y. Igarashi + + const std::vector vec = { + 2, 3, 5, 1, 4, 2, 6, + 8, 7, 9, 8, 11, 6, 13, + 12, 16, 15, 17, 18, + 20, 18, 19, 21, 19 + }; + CHECK( par(vec) == 5 ); + } + SECTION( "upper bound" ) { // The upper bound should correspond to the size of From 5fcc6d1149b3139e97da48b1af21668e18202a4b Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 27 Jul 2021 16:30:37 +0200 Subject: [PATCH 02/79] =?UTF-8?q?Add=20warning=20about=20future=20removal?= =?UTF-8?q?=20of=20O(n=C2=B2=20log=20n)=20probe::par?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/Measures-of-presortedness.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Measures-of-presortedness.md b/docs/Measures-of-presortedness.md index fae0f553..c76d9f44 100644 --- a/docs/Measures-of-presortedness.md +++ b/docs/Measures-of-presortedness.md @@ -232,6 +232,8 @@ The following definition is also given to determine whether a sequence is *p*-so `max_for_size`: |*X*| - 1 when the last element of *X* is smaller than the first one. +***WARNING: the O(n² log n) fallback will be removed in cpp-sort 2.0.0, which means that this algorithm won't be able to run without extra memory anymore.*** + ### *Rem* ```cpp From c5f10acff93b9d0c21ee0a8b83f67f20c40b3f39 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 27 Jul 2021 16:31:38 +0200 Subject: [PATCH 03/79] Fix graph of partial ordering of MOPs in doc --- docs/Measures-of-presortedness.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Measures-of-presortedness.md b/docs/Measures-of-presortedness.md index c76d9f44..31ac3c2f 100644 --- a/docs/Measures-of-presortedness.md +++ b/docs/Measures-of-presortedness.md @@ -29,7 +29,7 @@ The graph below shows the partial ordering of several measures of presortedness: - *m₀* is a measure of presortedness that always returns 0. - *m₀₁* is a measure of presortedness that returns 0 when *X* is sorted and 1 otherwise. -![Partial ordering of measures of presortedness](https://github.com/Morwenn/cpp-sort/wiki/images/mep-partial-ordering.png) +![Partial ordering of measures of presortedness](https://github.com/Morwenn/cpp-sort/wiki/images/mops-partial-ordering.png) This graph is more complete version of the one in *A framework for adaptive sorting* by O. Petersson and A. Moffat. The *Max* ≡ *Dis* equivalence comes from [*NeatSort - A practical adaptive algorithm*][neatsort] by La rocca & Cantone. The relations of *Mono* are empirically derived [original research][original-research]. From 952bc9cc7eff4762d90f820f1742df7126b7e289 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 27 Jul 2021 18:18:38 +0200 Subject: [PATCH 04/79] Make probe::par work with bidirectional iterators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Only the O(n) algorithm works with bidirectional iterators, which means that the fallback to the O(n² log n) algorithm only works with random-access iterators. --- docs/Measures-of-presortedness.md | 2 +- include/cpp-sort/probes/par.h | 80 ++++++++++++++++++++++++------- testsuite/probes/par.cpp | 21 ++++---- 3 files changed, 74 insertions(+), 29 deletions(-) diff --git a/docs/Measures-of-presortedness.md b/docs/Measures-of-presortedness.md index 31ac3c2f..ed3d3540 100644 --- a/docs/Measures-of-presortedness.md +++ b/docs/Measures-of-presortedness.md @@ -227,7 +227,7 @@ The following definition is also given to determine whether a sequence is *p*-so | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | -| n | n | Random-access | +| n | n | Bidirectional | | n² log n | 1 | Random-access | `max_for_size`: |*X*| - 1 when the last element of *X* is smaller than the first one. diff --git a/include/cpp-sort/probes/par.h b/include/cpp-sort/probes/par.h index f3278404..36819082 100644 --- a/include/cpp-sort/probes/par.h +++ b/include/cpp-sort/probes/par.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include "../detail/is_p_sorted.h" #include "../detail/iterator_traits.h" @@ -49,7 +50,7 @@ namespace probe } template - auto par_probe_algo(RandomAccessIterator first, RandomAccessIterator last, + auto new_par_probe_algo(RandomAccessIterator first, RandomAccessIterator last, cppsort::detail::difference_type_t size, Compare compare, Projection projection) -> ::cppsort::detail::difference_type_t @@ -103,33 +104,76 @@ namespace probe return *std::max_element(d.begin(), d.end()); } + template + auto par_probe_algo(RandomAccessIterator first, RandomAccessIterator last, + cppsort::detail::difference_type_t size, + Compare compare, Projection projection, + std::random_access_iterator_tag) + -> ::cppsort::detail::difference_type_t + { + try { + return new_par_probe_algo(first, last, size, compare, projection); + } catch (std::bad_alloc&) { + // Old O(n^2 log n) algorithm, kept to avoid a breaking + // when no extra memory is available, might be removed + // in the future + return legacy_par_probe_algo( + first, last, last - first, + std::move(compare), std::move(projection) + ); + } + } + + template + auto par_probe_algo(BidirectionalIterator first, BidirectionalIterator last, + cppsort::detail::difference_type_t size, + Compare compare, Projection projection, + std::bidirectional_iterator_tag) + -> ::cppsort::detail::difference_type_t + { + // The O(n^2 log n) fallback requires random-access iterators + return new_par_probe_algo(first, last, size, compare, projection); + } + struct par_impl { template< - typename RandomAccessIterator, + typename BidirectionalIterable, typename Compare = std::less<>, typename Projection = utility::identity, typename = std::enable_if_t< - is_projection_iterator_v + is_projection_v > > - auto operator()(RandomAccessIterator first, RandomAccessIterator last, + auto operator()(BidirectionalIterable&& iterable, Compare compare={}, Projection projection={}) const - -> cppsort::detail::difference_type_t + -> decltype(auto) { - try { - return par_probe_algo(first, last, last - first, - compare, projection); - } - catch (std::bad_alloc&) { - // Old O(n^2 log n) algorithm, kept to avoid a breaking - // when no extra memory is available, might be removed - // in the future - return legacy_par_probe_algo( - first, last, last - first, - std::move(compare), std::move(projection) - ); - } + using category = cppsort::detail::iterator_category_t< + cppsort::detail::remove_cvref_t + >; + return par_probe_algo(std::begin(iterable), std::end(iterable), + utility::size(iterable), + std::move(compare), std::move(projection), + category{}); + } + + template< + typename BidirectionalIterator, + typename Compare = std::less<>, + typename Projection = utility::identity, + typename = std::enable_if_t< + is_projection_iterator_v + > + > + auto operator()(BidirectionalIterator first, BidirectionalIterator last, + Compare compare={}, Projection projection={}) const + -> decltype(auto) + { + using category = cppsort::detail::iterator_category_t; + return par_probe_algo(first, last, std::distance(first, last), + std::move(compare), std::move(projection), + category{}); } template diff --git a/testsuite/probes/par.cpp b/testsuite/probes/par.cpp index 15611c0b..ee73671e 100644 --- a/testsuite/probes/par.cpp +++ b/testsuite/probes/par.cpp @@ -2,6 +2,7 @@ * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ +#include #include #include #include @@ -13,11 +14,11 @@ TEST_CASE( "presortedness measure: par", "[probe][par]" ) SECTION( "simple test" ) { - const std::vector vec = { 48, 43, 96, 44, 42, 34, 42, 57, 68, 69 }; - CHECK( par(vec) == 7 ); - CHECK( par(vec.begin(), vec.end()) == 7 ); + const std::list li = { 48, 43, 96, 44, 42, 34, 42, 57, 68, 69 }; + CHECK( par(li) == 7 ); + CHECK( par(li.begin(), li.end()) == 7 ); - std::vector> tricky(vec.begin(), vec.end()); + std::vector> tricky(li.begin(), li.end()); CHECK( par(tricky, &internal_compare::compare_to) == 7 ); } @@ -26,13 +27,13 @@ TEST_CASE( "presortedness measure: par", "[probe][par]" ) // Example from *Roughly Sorting: Sequential and Parallel Approach* // by T. Altman and Y. Igarashi - const std::vector vec = { + const std::list li = { 2, 3, 5, 1, 4, 2, 6, 8, 7, 9, 8, 11, 6, 13, 12, 16, 15, 17, 18, 20, 18, 19, 21, 19 }; - CHECK( par(vec) == 5 ); + CHECK( par(li) == 5 ); } SECTION( "upper bound" ) @@ -40,10 +41,10 @@ TEST_CASE( "presortedness measure: par", "[probe][par]" ) // The upper bound should correspond to the size of // the input sequence minus one - const std::vector vec = { 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; - auto max_n = par.max_for_size(vec.end() - vec.begin()); + const std::list li = { 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; + std::list::difference_type max_n = par.max_for_size(li.size()); CHECK( max_n == 10 ); - CHECK( par(vec) == max_n ); - CHECK( par(vec.begin(), vec.end()) == max_n ); + CHECK( par(li) == max_n ); + CHECK( par(li.begin(), li.end()) == max_n ); } } From c21d50bad024a469cb472ae8d4c053c0ebb76178 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 27 Jul 2021 19:15:40 +0200 Subject: [PATCH 05/79] Reduce memory allocations in probe::par --- include/cpp-sort/probes/par.h | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/include/cpp-sort/probes/par.h b/include/cpp-sort/probes/par.h index 36819082..f22b3e9a 100644 --- a/include/cpp-sort/probes/par.h +++ b/include/cpp-sort/probes/par.h @@ -20,6 +20,7 @@ #include #include #include +#include "../detail/immovable_vector.h" #include "../detail/is_p_sorted.h" #include "../detail/iterator_traits.h" @@ -67,31 +68,33 @@ namespace probe } // Algorithm LR - std::vector b = { first }; + cppsort::detail::immovable_vector b(size); + b.emplace_back(first); for (auto it = std::next(first) ; it != last ; ++it) { if (comp(proj(*b.back()), proj(*it))) { - b.push_back(it); + b.emplace_back(it); } else { - b.push_back(b.back()); + b.emplace_back(b.back()); } } // Algorithm RL - std::vector c = { std::prev(last) }; + cppsort::detail::immovable_vector c(size); + c.emplace_back(std::prev(last)); auto rfirst = std::make_reverse_iterator(last); auto rlast = std::make_reverse_iterator(first); for (auto it = std::next(rfirst) ; it != rlast ; ++it) { if (comp(proj(*it), proj(*c.back()))) { - c.push_back(std::prev(it.base())); + c.emplace_back(std::prev(it.base())); } else { - c.push_back(c.back()); + c.emplace_back(c.back()); } } std::reverse(c.begin(), c.end()); // Algorithm DM std::vector d = {}; - difference_type i = c.size(); + difference_type i = size; for (auto j = i ; j > 0 ; --j) { while (j <= i && i >= 1 && not comp(proj(*b[j - 1]), proj(*c[i - 1])) && (j == 1 || not comp(proj(*c[i - 1]), proj(*b[j - 2])))) { From 141efb2ac592361da4cff246212ea6416c6655c8 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 27 Jul 2021 23:31:55 +0200 Subject: [PATCH 06/79] Reduce memory use of probe::par Instead of computing all the values of DM and extracting the biggest one, we compute them on the fly and compare them to the current biggest element to avoid having to store O(n) values for DM. --- include/cpp-sort/probes/par.h | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/include/cpp-sort/probes/par.h b/include/cpp-sort/probes/par.h index f22b3e9a..070cd324 100644 --- a/include/cpp-sort/probes/par.h +++ b/include/cpp-sort/probes/par.h @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include @@ -92,19 +91,18 @@ namespace probe } std::reverse(c.begin(), c.end()); - // Algorithm DM - std::vector d = {}; + // Algorithm DM, without extra storage + difference_type res = 0; difference_type i = size; for (auto j = i ; j > 0 ; --j) { while (j <= i && i >= 1 && not comp(proj(*b[j - 1]), proj(*c[i - 1])) && (j == 1 || not comp(proj(*c[i - 1]), proj(*b[j - 2])))) { - d.push_back(i - j); + res = std::max(res, i - j); --i; } } - // Compute radius = max(dm) - return *std::max_element(d.begin(), d.end()); + return res; } template From bf24ed380b9e344a0f4dc8405d129ca6da21f42d Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 29 Jul 2021 12:11:23 +0200 Subject: [PATCH 07/79] =?UTF-8?q?Make=20is=5Fp=5Fsorter=20O(n)=20instead?= =?UTF-8?q?=20of=20O(n=C2=B2)=20(#85)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This in turns makes the probe::par fallback algorithm run in O(n log n) time and O(1) space instead of O(n² log n) time. That change makes the fallback algorithm worth keeping no matter what since O(n log n) is reasonable enough a suprise - the deprecation warning has been removed accordingly. Thanks a lot to Control from The Studio for showing me that it was actually easy enough to make is_p_sorted O(n) and for giving me the necessary insights to make it so. --- docs/Measures-of-presortedness.md | 8 ++++++-- include/cpp-sort/detail/is_p_sorted.h | 16 +++++++++------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/docs/Measures-of-presortedness.md b/docs/Measures-of-presortedness.md index ed3d3540..5534f165 100644 --- a/docs/Measures-of-presortedness.md +++ b/docs/Measures-of-presortedness.md @@ -228,11 +228,15 @@ The following definition is also given to determine whether a sequence is *p*-so | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | | n | n | Bidirectional | -| n² log n | 1 | Random-access | +| n log n | 1 | Random-access | + +When enough memory is available, `probe::par` runs in O(n), otherwise it falls back to an O(n log n) algorithm that does not require extra memory (the fallback only works with random-access iterator). `max_for_size`: |*X*| - 1 when the last element of *X* is smaller than the first one. -***WARNING: the O(n² log n) fallback will be removed in cpp-sort 2.0.0, which means that this algorithm won't be able to run without extra memory anymore.*** +*Changed in version 1.12.0:* `probe::par` now runs in O(n) time and O(n) space, and falls back to a O(n log n) time O(1) algorithm when there isn't enough heap memory available. + +*Changed in version 1.12.0:* `probe::par` now works with bidirectional iterators (except for the fallback). ### *Rem* diff --git a/include/cpp-sort/detail/is_p_sorted.h b/include/cpp-sort/detail/is_p_sorted.h index a39c1c10..2f858bd8 100644 --- a/include/cpp-sort/detail/is_p_sorted.h +++ b/include/cpp-sort/detail/is_p_sorted.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2017 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_IS_P_SORTED_H_ @@ -24,13 +24,15 @@ namespace detail auto&& comp = utility::as_function(compare); auto&& proj = utility::as_function(projection); - for (auto it1 = first + p ; it1 != last ; ++it1) { - auto&& value = proj(*it1); - for (auto it2 = first ; it2 != it1 - p ; ++it2) { - if (comp(value, proj(*it2))) { - return false; - } + auto max_it = first; + for (auto it1 = first + p + 1 ; it1 != last ; ++it1) { + if (comp(proj(*max_it), proj(*first))) { + max_it = first; } + if (comp(proj(*it1), proj(*max_it))) { + return false; + } + ++first; } return true; } From 729e319eae03dadce483516a3bc91305245dcefc Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 29 Jul 2021 14:38:48 +0200 Subject: [PATCH 08/79] Make probe::par work with forward iterators is_p_sorted and the O(n log n) algorithm relying on it were changed to work with forward iterators. probe::par now works with forward iterators, but can only use the O(n) algorithm for bidirectional iterators and their refinements. --- docs/Measures-of-presortedness.md | 8 +-- include/cpp-sort/detail/is_p_sorted.h | 9 +-- include/cpp-sort/probes/par.h | 65 ++++++++++--------- .../every_probe_heap_memory_exhaustion.cpp | 2 + testsuite/probes/par.cpp | 11 ++-- 5 files changed, 50 insertions(+), 45 deletions(-) diff --git a/docs/Measures-of-presortedness.md b/docs/Measures-of-presortedness.md index 5534f165..4df69d9c 100644 --- a/docs/Measures-of-presortedness.md +++ b/docs/Measures-of-presortedness.md @@ -228,15 +228,15 @@ The following definition is also given to determine whether a sequence is *p*-so | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | | n | n | Bidirectional | -| n log n | 1 | Random-access | +| n log n | 1 | Forward | -When enough memory is available, `probe::par` runs in O(n), otherwise it falls back to an O(n log n) algorithm that does not require extra memory (the fallback only works with random-access iterator). +When enough memory is available, `probe::par` runs in O(n), otherwise it falls back to an O(n log n) algorithm that does not require extra memory. If a forward iterator is passed, the O(n log n) algorithm is always used. `max_for_size`: |*X*| - 1 when the last element of *X* is smaller than the first one. -*Changed in version 1.12.0:* `probe::par` now runs in O(n) time and O(n) space, and falls back to a O(n log n) time O(1) algorithm when there isn't enough heap memory available. +*Changed in version 1.12.0:* `probe::par` now runs in O(n) time and O(n) space, and falls back to a O(n log n) time O(1) space algorithm when there isn't enough heap memory available. -*Changed in version 1.12.0:* `probe::par` now works with bidirectional iterators (except for the fallback). +*Changed in version 1.12.0:* `probe::par` now works with forward iterators. ### *Rem* diff --git a/include/cpp-sort/detail/is_p_sorted.h b/include/cpp-sort/detail/is_p_sorted.h index 2f858bd8..5a39ce4e 100644 --- a/include/cpp-sort/detail/is_p_sorted.h +++ b/include/cpp-sort/detail/is_p_sorted.h @@ -15,17 +15,18 @@ namespace cppsort { namespace detail { - template - auto is_p_sorted(RandomAccessIterator first, RandomAccessIterator last, - difference_type_t p, + template + auto is_p_sorted(ForwardIterator first, ForwardIterator last, ForwardIterator pth, Compare compare, Projection projection) -> bool { auto&& comp = utility::as_function(compare); auto&& proj = utility::as_function(projection); + // pth is the iterator such as pth - first == p + auto max_it = first; - for (auto it1 = first + p + 1 ; it1 != last ; ++it1) { + for (auto it1 = std::next(pth); it1 != last; ++it1) { if (comp(proj(*max_it), proj(*first))) { max_it = first; } diff --git a/include/cpp-sort/probes/par.h b/include/cpp-sort/probes/par.h index 070cd324..3f18d54c 100644 --- a/include/cpp-sort/probes/par.h +++ b/include/cpp-sort/probes/par.h @@ -35,27 +35,33 @@ namespace probe Compare compare, Projection projection) -> ::cppsort::detail::difference_type_t { + // Simple algorithm in O(n log n) time and O(1) space + auto res = 0; + auto res_it = first; while (size > 0) { auto p = res; + auto pth = res_it; p += size / 2; - if (cppsort::detail::is_p_sorted(first, last, p, compare, projection)) { + std::advance(pth, size / 2); + if (cppsort::detail::is_p_sorted(first, last, pth, compare, projection)) { size /= 2; } else { res = ++p; + res_it = ++pth; size -= size / 2 + 1; } } return res; } - template - auto new_par_probe_algo(RandomAccessIterator first, RandomAccessIterator last, - cppsort::detail::difference_type_t size, + template + auto new_par_probe_algo(BidirectionalIterator first, BidirectionalIterator last, + cppsort::detail::difference_type_t size, Compare compare, Projection projection) - -> ::cppsort::detail::difference_type_t + -> ::cppsort::detail::difference_type_t { - using difference_type = ::cppsort::detail::difference_type_t; + using difference_type = ::cppsort::detail::difference_type_t; auto&& comp = utility::as_function(compare); auto&& proj = utility::as_function(projection); @@ -67,7 +73,7 @@ namespace probe } // Algorithm LR - cppsort::detail::immovable_vector b(size); + cppsort::detail::immovable_vector b(size); b.emplace_back(first); for (auto it = std::next(first) ; it != last ; ++it) { if (comp(proj(*b.back()), proj(*it))) { @@ -78,7 +84,7 @@ namespace probe } // Algorithm RL - cppsort::detail::immovable_vector c(size); + cppsort::detail::immovable_vector c(size); c.emplace_back(std::prev(last)); auto rfirst = std::make_reverse_iterator(last); auto rlast = std::make_reverse_iterator(first); @@ -105,49 +111,44 @@ namespace probe return res; } - template - auto par_probe_algo(RandomAccessIterator first, RandomAccessIterator last, - cppsort::detail::difference_type_t size, + template + auto par_probe_algo(BidirectionalIterator first, BidirectionalIterator last, + cppsort::detail::difference_type_t size, Compare compare, Projection projection, - std::random_access_iterator_tag) - -> ::cppsort::detail::difference_type_t + std::bidirectional_iterator_tag) + -> ::cppsort::detail::difference_type_t { try { return new_par_probe_algo(first, last, size, compare, projection); } catch (std::bad_alloc&) { - // Old O(n^2 log n) algorithm, kept to avoid a breaking - // when no extra memory is available, might be removed - // in the future return legacy_par_probe_algo( - first, last, last - first, + first, last, size, std::move(compare), std::move(projection) ); } } - template - auto par_probe_algo(BidirectionalIterator first, BidirectionalIterator last, - cppsort::detail::difference_type_t size, + template + auto par_probe_algo(ForwardIterator first, ForwardIterator last, + cppsort::detail::difference_type_t size, Compare compare, Projection projection, - std::bidirectional_iterator_tag) - -> ::cppsort::detail::difference_type_t + std::forward_iterator_tag) + -> ::cppsort::detail::difference_type_t { - // The O(n^2 log n) fallback requires random-access iterators - return new_par_probe_algo(first, last, size, compare, projection); + return legacy_par_probe_algo(first, last, size, compare, projection); } struct par_impl { template< - typename BidirectionalIterable, + typename ForwardIterable, typename Compare = std::less<>, typename Projection = utility::identity, typename = std::enable_if_t< - is_projection_v + is_projection_v > > - auto operator()(BidirectionalIterable&& iterable, - Compare compare={}, Projection projection={}) const + auto operator()(ForwardIterable&& iterable, Compare compare={}, Projection projection={}) const -> decltype(auto) { using category = cppsort::detail::iterator_category_t< @@ -160,18 +161,18 @@ namespace probe } template< - typename BidirectionalIterator, + typename ForwardIterator, typename Compare = std::less<>, typename Projection = utility::identity, typename = std::enable_if_t< - is_projection_iterator_v + is_projection_iterator_v > > - auto operator()(BidirectionalIterator first, BidirectionalIterator last, + auto operator()(ForwardIterator first, ForwardIterator last, Compare compare={}, Projection projection={}) const -> decltype(auto) { - using category = cppsort::detail::iterator_category_t; + using category = cppsort::detail::iterator_category_t; return par_probe_algo(first, last, std::distance(first, last), std::move(compare), std::move(projection), category{}); diff --git a/testsuite/probes/every_probe_heap_memory_exhaustion.cpp b/testsuite/probes/every_probe_heap_memory_exhaustion.cpp index a1569c4a..13fe1d71 100644 --- a/testsuite/probes/every_probe_heap_memory_exhaustion.cpp +++ b/testsuite/probes/every_probe_heap_memory_exhaustion.cpp @@ -43,6 +43,7 @@ TEMPLATE_TEST_CASE( "heap exhaustion for bidirectional probes", "[probe][heap_ex decltype(cppsort::probe::dis), decltype(cppsort::probe::mono), decltype(cppsort::probe::osc), + decltype(cppsort::probe::par), decltype(cppsort::probe::runs) ) { std::list collection; @@ -62,6 +63,7 @@ TEMPLATE_TEST_CASE( "heap exhaustion for forward probes", "[probe][heap_exhausti decltype(cppsort::probe::dis), decltype(cppsort::probe::mono), decltype(cppsort::probe::osc), + decltype(cppsort::probe::par), decltype(cppsort::probe::runs) ) { std::forward_list collection; diff --git a/testsuite/probes/par.cpp b/testsuite/probes/par.cpp index ee73671e..98b6168b 100644 --- a/testsuite/probes/par.cpp +++ b/testsuite/probes/par.cpp @@ -2,10 +2,11 @@ * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ -#include +#include #include #include #include +#include #include TEST_CASE( "presortedness measure: par", "[probe][par]" ) @@ -14,7 +15,7 @@ TEST_CASE( "presortedness measure: par", "[probe][par]" ) SECTION( "simple test" ) { - const std::list li = { 48, 43, 96, 44, 42, 34, 42, 57, 68, 69 }; + const std::forward_list li = { 48, 43, 96, 44, 42, 34, 42, 57, 68, 69 }; CHECK( par(li) == 7 ); CHECK( par(li.begin(), li.end()) == 7 ); @@ -27,7 +28,7 @@ TEST_CASE( "presortedness measure: par", "[probe][par]" ) // Example from *Roughly Sorting: Sequential and Parallel Approach* // by T. Altman and Y. Igarashi - const std::list li = { + const std::forward_list li = { 2, 3, 5, 1, 4, 2, 6, 8, 7, 9, 8, 11, 6, 13, 12, 16, 15, 17, 18, @@ -41,8 +42,8 @@ TEST_CASE( "presortedness measure: par", "[probe][par]" ) // The upper bound should correspond to the size of // the input sequence minus one - const std::list li = { 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; - std::list::difference_type max_n = par.max_for_size(li.size()); + const std::forward_list li = { 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; + auto max_n = par.max_for_size(cppsort::utility::size(li)); CHECK( max_n == 10 ); CHECK( par(li) == max_n ); CHECK( par(li.begin(), li.end()) == max_n ); From d2b91a85c6b83fb2cb843ad2c1691e98598ba1b9 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 29 Jul 2021 15:44:32 +0200 Subject: [PATCH 09/79] New ascending_duplicates distribution to simplify tests --- testsuite/probes/exc.cpp | 4 +--- testsuite/probes/ham.cpp | 4 +--- testsuite/probes/max.cpp | 4 +--- testsuite/testing-tools/distributions.h | 17 +++++++++++++++++ 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/testsuite/probes/exc.cpp b/testsuite/probes/exc.cpp index 05003d6f..93bdc8f0 100644 --- a/testsuite/probes/exc.cpp +++ b/testsuite/probes/exc.cpp @@ -2,7 +2,6 @@ * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ -#include #include #include #include @@ -46,10 +45,9 @@ TEST_CASE( "presortedness measure: exc", "[probe][exc]" ) { std::vector collection; collection.reserve(100); - auto distribution = dist::ascending_sawtooth{}; + auto distribution = dist::ascending_duplicates{}; distribution(std::back_inserter(collection), 100); - std::sort(collection.begin(), collection.end()); CHECK( exc(collection) == 0 ); } } diff --git a/testsuite/probes/ham.cpp b/testsuite/probes/ham.cpp index bb87e640..4a3ce7ca 100644 --- a/testsuite/probes/ham.cpp +++ b/testsuite/probes/ham.cpp @@ -2,7 +2,6 @@ * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ -#include #include #include #include @@ -42,10 +41,9 @@ TEST_CASE( "presortedness measure: ham", "[probe][ham]" ) { std::vector collection; collection.reserve(100); - auto distribution = dist::ascending_sawtooth{}; + auto distribution = dist::ascending_duplicates{}; distribution(std::back_inserter(collection), 100); - std::sort(collection.begin(), collection.end()); CHECK( ham(collection) == 0 ); } } diff --git a/testsuite/probes/max.cpp b/testsuite/probes/max.cpp index 24cd41b0..f866f3a0 100644 --- a/testsuite/probes/max.cpp +++ b/testsuite/probes/max.cpp @@ -2,7 +2,6 @@ * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ -#include #include #include #include @@ -42,10 +41,9 @@ TEST_CASE( "presortedness measure: max", "[probe][max]" ) { std::vector collection; collection.reserve(100); - auto distribution = dist::ascending_sawtooth{}; + auto distribution = dist::ascending_duplicates{}; distribution(std::back_inserter(collection), 100); - std::sort(collection.begin(), collection.end()); CHECK( (max)(collection) == 0 ); } } diff --git a/testsuite/testing-tools/distributions.h b/testsuite/testing-tools/distributions.h index 61d1d011..0c3c6d20 100644 --- a/testsuite/testing-tools/distributions.h +++ b/testsuite/testing-tools/distributions.h @@ -115,6 +115,23 @@ namespace dist } }; + struct ascending_duplicates: + distribution + { + // Ascending (sorted) distribution with series of 10 + // times the same integer value, used to test specific + // algorithms against inputs with duplicate values + + template + auto operator()(OutputIterator out, long long int size) const + -> void + { + for (long long int i = 0 ; i < size ; ++i) { + *out++ = i / 10; + } + } + }; + struct pipe_organ: distribution { From c5912573635df1600af2887ef20e1d9a1a3fea48 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 31 Jul 2021 16:37:44 +0200 Subject: [PATCH 10/79] Improve probe::dis, deprecate probe::par In *Right invariant metrics and measures of presortedness*, Estivill-Castro, Mannila and Wood mention that Par(X)=Dis(X) for all X. After checking, it appears that probe::par and probe::dis do always return the same results despite having been implemented differently to match different definitions. Estivill-Castro and Wood then stop using Par(X) and use Dis(X) consistently in all their subsequent works, so it makes sense to drop probe::par and only keep probe::dis in cpp-sort. This commit deprecates probe::par - which will be removed in a future breaking release -, and changes probe::dis to use the current implementation of probe::par, which has a better time complexity. Thanks a lot to Control from The Studio, who first suggested that Dis and Par were the same measure of presortedness, and pushed me to look into it. --- docs/Measures-of-presortedness.md | 31 +--- docs/images/mops-partial-ordering.png | Bin 20644 -> 29045 bytes include/cpp-sort/probes/dis.h | 136 +++++++++++--- include/cpp-sort/probes/par.h | 174 +----------------- testsuite/CMakeLists.txt | 1 - testsuite/probes/dis.cpp | 38 +++- testsuite/probes/every_probe_common.cpp | 2 - .../every_probe_heap_memory_exhaustion.cpp | 5 +- .../every_probe_move_compare_projection.cpp | 2 - testsuite/probes/par.cpp | 51 ----- testsuite/probes/relations.cpp | 14 +- tools/mops-partial-ordering.tex | 4 +- tools/test_failing_sorter.cpp | 1 - 13 files changed, 165 insertions(+), 294 deletions(-) delete mode 100644 testsuite/probes/par.cpp diff --git a/docs/Measures-of-presortedness.md b/docs/Measures-of-presortedness.md index 4df69d9c..4c4fa47e 100644 --- a/docs/Measures-of-presortedness.md +++ b/docs/Measures-of-presortedness.md @@ -95,14 +95,17 @@ Computes the maximum distance determined by an inversion. | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | -| n² | 1 | Forward | +| n | n | Bidirectional | +| n log n | 1 | Forward | -`max_for_size`: |*X*| - 1 when *X* is sorted in reverse order. +When enough memory is available, `probe::dis` runs in O(n), otherwise it falls back to an O(n log n) algorithm that does not require extra memory. If forward iterators are passed, the O(n log n) algorithm is always used. -*Warning: this algorithm might be noticeably slower when the passed iterable is not random-access.* +`max_for_size`: |*X*| - 1 when the last element of *X* is smaller than the first one. *Changed in version 1.8.0:* `probe::dis` is now O(n²) instead of accidentally being O(n³) when passed forward or bidirectional iterators. +*Changed in version 1.12.0:* `probe::dis` is now O(n log n) instead of O(n²). When sorting bidirectional iterators, if enough heap memory is available, it runs in O(n) time and O(n) space. + ### *Enc* ```cpp @@ -217,26 +220,7 @@ Computes the *Oscillation* measure described by Levcopoulos and Petersson in *Ad #include ``` -Computes the *Par* measure described by Estivill-Castro and Wood in *A New Measure of Presortedness* as follows: - -> *Par(X)* = min { *p* | *X* is *p*-sorted } - -The following definition is also given to determine whether a sequence is *p*-sorted: - -> *X* is *p*-sorted iff for all *i*, *j* ∈ {1, 2, ..., |*X*|}, *i* - *j* > *p* implies *Xj* ≤ *Xi*. - -| Complexity | Memory | Iterators | -| ----------- | ----------- | ------------- | -| n | n | Bidirectional | -| n log n | 1 | Forward | - -When enough memory is available, `probe::par` runs in O(n), otherwise it falls back to an O(n log n) algorithm that does not require extra memory. If a forward iterator is passed, the O(n log n) algorithm is always used. - -`max_for_size`: |*X*| - 1 when the last element of *X* is smaller than the first one. - -*Changed in version 1.12.0:* `probe::par` now runs in O(n) time and O(n) space, and falls back to a O(n log n) time O(1) space algorithm when there isn't enough heap memory available. - -*Changed in version 1.12.0:* `probe::par` now works with forward iterators. +***WARNING:** `probe::par` is deprecated since version 1.12.0 and removed in version 2.0.0, use [`probe::dis`][probe-dis] instead.* ### *Rem* @@ -289,4 +273,5 @@ Computes the minimum number of non-decreasing subsequences (of possibly not adja [longest-increasing-subsequence]: https://en.wikipedia.org/wiki/Longest_increasing_subsequence [neatsort]: https://arxiv.org/pdf/1407.6183.pdf [original-research]: https://github.com/Morwenn/cpp-sort/wiki/Original-research#partial-ordering-of-mono + [probe-dis]: https://github.com/Morwenn/cpp-sort/wiki/Measures-of-presortedness#dis [sort-race]: https://arxiv.org/ftp/arxiv/papers/1609/1609.04471.pdf diff --git a/docs/images/mops-partial-ordering.png b/docs/images/mops-partial-ordering.png index 258cfd966e38a4aae31ca7d5ffa2ad749f169b5d..d8f865c0ce7dc0912eeaffc055a1ca361737a947 100644 GIT binary patch literal 29045 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 #include #include +#include #include +#include #include #include -#include #include #include #include +#include "../detail/immovable_vector.h" +#include "../detail/is_p_sorted.h" #include "../detail/iterator_traits.h" namespace cppsort @@ -26,37 +29,113 @@ namespace probe { namespace detail { - template - auto dis_probe_algo(ForwardIterator first, ForwardIterator last, - cppsort::detail::difference_type_t size, - Compare compare, Projection projection) - -> ::cppsort::detail::difference_type_t + template + auto inplace_dis_probe_algo(RandomAccessIterator first, RandomAccessIterator last, + cppsort::detail::difference_type_t size, + Compare compare, Projection projection) + -> ::cppsort::detail::difference_type_t { - using difference_type = cppsort::detail::difference_type_t; + // Simple algorithm in O(n log n) time and O(1) space + + auto res = 0; + auto res_it = first; + while (size > 0) { + auto p = res; + auto pth = res_it; + p += size / 2; + std::advance(pth, size / 2); + if (cppsort::detail::is_p_sorted(first, last, pth, compare, projection)) { + size /= 2; + } else { + res = ++p; + res_it = ++pth; + size -= size / 2 + 1; + } + } + return res; + } + + template + auto allocating_dis_probe_algo(BidirectionalIterator first, BidirectionalIterator last, + cppsort::detail::difference_type_t size, + Compare compare, Projection projection) + -> ::cppsort::detail::difference_type_t + { + using difference_type = ::cppsort::detail::difference_type_t; auto&& comp = utility::as_function(compare); auto&& proj = utility::as_function(projection); + // Algorithm described in *Roughly Sorting: Sequential and Parallel Approach* + // by T. Altman and Y. Igarashi + if (size < 2) { return 0; } - difference_type max_dist = 0; - auto it1 = first; - do { - auto&& value = proj(*it1); - - difference_type dist = 1; // Distance between it1 and it2 - for (auto it2 = std::next(it1) ; it2 != last ; ++it2) { - if (comp(proj(*it2), value)) { - max_dist = (std::max)(max_dist, dist); - } - ++dist; + // Algorithm LR + cppsort::detail::immovable_vector b(size); + b.emplace_back(first); + for (auto it = std::next(first) ; it != last ; ++it) { + if (comp(proj(*b.back()), proj(*it))) { + b.emplace_back(it); + } else { + b.emplace_back(b.back()); + } + } + + // Algorithm RL + cppsort::detail::immovable_vector c(size); + c.emplace_back(std::prev(last)); + auto rfirst = std::make_reverse_iterator(last); + auto rlast = std::make_reverse_iterator(first); + for (auto it = std::next(rfirst) ; it != rlast ; ++it) { + if (comp(proj(*it), proj(*c.back()))) { + c.emplace_back(std::prev(it.base())); + } else { + c.emplace_back(c.back()); + } + } + std::reverse(c.begin(), c.end()); + + // Algorithm DM, without extra storage + difference_type res = 0; + difference_type i = size; + for (auto j = i ; j > 0 ; --j) { + while (j <= i && i >= 1 && not comp(proj(*b[j - 1]), proj(*c[i - 1])) + && (j == 1 || not comp(proj(*c[i - 1]), proj(*b[j - 2])))) { + res = std::max(res, i - j); + --i; } + } + + return res; + } - ++it1; - --size; - } while (max_dist < size); - return max_dist; + template + auto dis_probe_algo(BidirectionalIterator first, BidirectionalIterator last, + cppsort::detail::difference_type_t size, + Compare compare, Projection projection, + std::bidirectional_iterator_tag) + -> ::cppsort::detail::difference_type_t + { + try { + return allocating_dis_probe_algo(first, last, size, compare, projection); + } catch (std::bad_alloc&) { + return inplace_dis_probe_algo( + first, last, size, + std::move(compare), std::move(projection) + ); + } + } + + template + auto dis_probe_algo(ForwardIterator first, ForwardIterator last, + cppsort::detail::difference_type_t size, + Compare compare, Projection projection, + std::forward_iterator_tag) + -> ::cppsort::detail::difference_type_t + { + return inplace_dis_probe_algo(first, last, size, compare, projection); } struct dis_impl @@ -69,13 +148,16 @@ namespace probe is_projection_v > > - auto operator()(ForwardIterable&& iterable, - Compare compare={}, Projection projection={}) const + auto operator()(ForwardIterable&& iterable, Compare compare={}, Projection projection={}) const -> decltype(auto) { + using category = cppsort::detail::iterator_category_t< + cppsort::detail::remove_cvref_t + >; return dis_probe_algo(std::begin(iterable), std::end(iterable), utility::size(iterable), - std::move(compare), std::move(projection)); + std::move(compare), std::move(projection), + category{}); } template< @@ -90,8 +172,10 @@ namespace probe Compare compare={}, Projection projection={}) const -> decltype(auto) { + using category = cppsort::detail::iterator_category_t; return dis_probe_algo(first, last, std::distance(first, last), - std::move(compare), std::move(projection)); + std::move(compare), std::move(projection), + category{}); } template diff --git a/include/cpp-sort/probes/par.h b/include/cpp-sort/probes/par.h index 3f18d54c..14635eab 100644 --- a/include/cpp-sort/probes/par.h +++ b/include/cpp-sort/probes/par.h @@ -8,20 +8,10 @@ //////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// -#include -#include -#include -#include -#include -#include +#include #include -#include -#include -#include #include -#include "../detail/immovable_vector.h" -#include "../detail/is_p_sorted.h" -#include "../detail/iterator_traits.h" +#include "../detail/attributes.h" namespace cppsort { @@ -29,166 +19,14 @@ namespace probe { namespace detail { - template - auto legacy_par_probe_algo(RandomAccessIterator first, RandomAccessIterator last, - cppsort::detail::difference_type_t size, - Compare compare, Projection projection) - -> ::cppsort::detail::difference_type_t - { - // Simple algorithm in O(n log n) time and O(1) space - - auto res = 0; - auto res_it = first; - while (size > 0) { - auto p = res; - auto pth = res_it; - p += size / 2; - std::advance(pth, size / 2); - if (cppsort::detail::is_p_sorted(first, last, pth, compare, projection)) { - size /= 2; - } else { - res = ++p; - res_it = ++pth; - size -= size / 2 + 1; - } - } - return res; - } - - template - auto new_par_probe_algo(BidirectionalIterator first, BidirectionalIterator last, - cppsort::detail::difference_type_t size, - Compare compare, Projection projection) - -> ::cppsort::detail::difference_type_t - { - using difference_type = ::cppsort::detail::difference_type_t; - auto&& comp = utility::as_function(compare); - auto&& proj = utility::as_function(projection); - - // Algorithm described in *Roughly Sorting: Sequential and Parallel Approach* - // by T. Altman and Y. Igarashi - - if (size < 2) { - return 0; - } - - // Algorithm LR - cppsort::detail::immovable_vector b(size); - b.emplace_back(first); - for (auto it = std::next(first) ; it != last ; ++it) { - if (comp(proj(*b.back()), proj(*it))) { - b.emplace_back(it); - } else { - b.emplace_back(b.back()); - } - } - - // Algorithm RL - cppsort::detail::immovable_vector c(size); - c.emplace_back(std::prev(last)); - auto rfirst = std::make_reverse_iterator(last); - auto rlast = std::make_reverse_iterator(first); - for (auto it = std::next(rfirst) ; it != rlast ; ++it) { - if (comp(proj(*it), proj(*c.back()))) { - c.emplace_back(std::prev(it.base())); - } else { - c.emplace_back(c.back()); - } - } - std::reverse(c.begin(), c.end()); - - // Algorithm DM, without extra storage - difference_type res = 0; - difference_type i = size; - for (auto j = i ; j > 0 ; --j) { - while (j <= i && i >= 1 && not comp(proj(*b[j - 1]), proj(*c[i - 1])) - && (j == 1 || not comp(proj(*c[i - 1]), proj(*b[j - 2])))) { - res = std::max(res, i - j); - --i; - } - } - - return res; - } - - template - auto par_probe_algo(BidirectionalIterator first, BidirectionalIterator last, - cppsort::detail::difference_type_t size, - Compare compare, Projection projection, - std::bidirectional_iterator_tag) - -> ::cppsort::detail::difference_type_t - { - try { - return new_par_probe_algo(first, last, size, compare, projection); - } catch (std::bad_alloc&) { - return legacy_par_probe_algo( - first, last, size, - std::move(compare), std::move(projection) - ); - } - } - - template - auto par_probe_algo(ForwardIterator first, ForwardIterator last, - cppsort::detail::difference_type_t size, - Compare compare, Projection projection, - std::forward_iterator_tag) - -> ::cppsort::detail::difference_type_t - { - return legacy_par_probe_algo(first, last, size, compare, projection); - } - - struct par_impl - { - template< - typename ForwardIterable, - typename Compare = std::less<>, - typename Projection = utility::identity, - typename = std::enable_if_t< - is_projection_v - > - > - auto operator()(ForwardIterable&& iterable, Compare compare={}, Projection projection={}) const - -> decltype(auto) - { - using category = cppsort::detail::iterator_category_t< - cppsort::detail::remove_cvref_t - >; - return par_probe_algo(std::begin(iterable), std::end(iterable), - utility::size(iterable), - std::move(compare), std::move(projection), - category{}); - } - - template< - typename ForwardIterator, - typename Compare = std::less<>, - typename Projection = utility::identity, - typename = std::enable_if_t< - is_projection_iterator_v - > - > - auto operator()(ForwardIterator first, ForwardIterator last, - Compare compare={}, Projection projection={}) const - -> decltype(auto) - { - using category = cppsort::detail::iterator_category_t; - return par_probe_algo(first, last, std::distance(first, last), - std::move(compare), std::move(projection), - category{}); - } - - template - static constexpr auto max_for_size(Integer n) - -> Integer - { - return n == 0 ? 0 : n - 1; - } - }; + struct par_impl: + dis_impl + {}; } namespace { + CPPSORT_DEPRECATED("probe::par is deprecated and will be removed in version 2.0.0, use probe::dis instead") constexpr auto&& par = utility::static_const< sorter_facade >::value; diff --git a/testsuite/CMakeLists.txt b/testsuite/CMakeLists.txt index 88a4954b..b6e6501f 100644 --- a/testsuite/CMakeLists.txt +++ b/testsuite/CMakeLists.txt @@ -184,7 +184,6 @@ add_executable(main-tests probes/max.cpp probes/mono.cpp probes/osc.cpp - probes/par.cpp probes/rem.cpp probes/runs.cpp probes/sus.cpp diff --git a/testsuite/probes/dis.cpp b/testsuite/probes/dis.cpp index cb5f26a9..26b841c3 100644 --- a/testsuite/probes/dis.cpp +++ b/testsuite/probes/dis.cpp @@ -13,14 +13,38 @@ TEST_CASE( "presortedness measure: dis", "[probe][dis]" ) { using cppsort::probe::dis; - SECTION( "simple test" ) + SECTION( "simple tests" ) { - std::forward_list li = { 47, 53, 46, 41, 59, 81, 74, 97, 100, 45 }; - CHECK( dis(li) == 9 ); - CHECK( dis(li.begin(), li.end()) == 9 ); + { + std::forward_list li = { 47, 53, 46, 41, 59, 81, 74, 97, 100, 45 }; + CHECK( dis(li) == 9 ); + CHECK( dis(li.begin(), li.end()) == 9 ); - std::vector> tricky(li.begin(), li.end()); - CHECK( dis(tricky, &internal_compare::compare_to) == 9 ); + std::vector> tricky(li.begin(), li.end()); + CHECK( dis(tricky, &internal_compare::compare_to) == 9 ); + } + { + const std::forward_list li = { 48, 43, 96, 44, 42, 34, 42, 57, 68, 69 }; + CHECK( dis(li) == 7 ); + CHECK( dis(li.begin(), li.end()) == 7 ); + + std::vector> tricky(li.begin(), li.end()); + CHECK( dis(tricky, &internal_compare::compare_to) == 7 ); + } + } + + SECTION( "roughly sorting test" ) + { + // Example from *Roughly Sorting: Sequential and Parallel Approach* + // by T. Altman and Y. Igarashi + + const std::forward_list li = { + 2, 3, 5, 1, 4, 2, 6, + 8, 7, 9, 8, 11, 6, 13, + 12, 16, 15, 17, 18, + 20, 18, 19, 21, 19 + }; + CHECK( dis(li) == 5 ); } SECTION( "upper bound" ) @@ -28,7 +52,7 @@ TEST_CASE( "presortedness measure: dis", "[probe][dis]" ) // The upper bound should correspond to the size of // the input sequence minus one - std::forward_list li = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; + std::forward_list li = { 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; auto max_n = dis.max_for_size(cppsort::utility::size(li)); CHECK( max_n == 10 ); CHECK( dis(li) == max_n ); diff --git a/testsuite/probes/every_probe_common.cpp b/testsuite/probes/every_probe_common.cpp index 532cea8d..5a048118 100644 --- a/testsuite/probes/every_probe_common.cpp +++ b/testsuite/probes/every_probe_common.cpp @@ -21,7 +21,6 @@ TEMPLATE_TEST_CASE( "test every probe with all_equal distribution", "[probe]", decltype(cppsort::probe::max), decltype(cppsort::probe::mono), decltype(cppsort::probe::osc), - decltype(cppsort::probe::par), decltype(cppsort::probe::rem), decltype(cppsort::probe::runs), decltype(cppsort::probe::sus) ) @@ -44,7 +43,6 @@ TEMPLATE_TEST_CASE( "test every probe with a sorted collection", "[probe]", decltype(cppsort::probe::max), decltype(cppsort::probe::mono), decltype(cppsort::probe::osc), - decltype(cppsort::probe::par), decltype(cppsort::probe::rem), decltype(cppsort::probe::runs), decltype(cppsort::probe::sus) ) diff --git a/testsuite/probes/every_probe_heap_memory_exhaustion.cpp b/testsuite/probes/every_probe_heap_memory_exhaustion.cpp index 13fe1d71..1e6764de 100644 --- a/testsuite/probes/every_probe_heap_memory_exhaustion.cpp +++ b/testsuite/probes/every_probe_heap_memory_exhaustion.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Morwenn + * Copyright (c) 2020-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -23,7 +23,6 @@ TEMPLATE_TEST_CASE( "heap exhaustion for random-access probes", "[probe][heap_ex decltype(cppsort::probe::dis), decltype(cppsort::probe::mono), decltype(cppsort::probe::osc), - decltype(cppsort::probe::par), decltype(cppsort::probe::runs) ) { std::vector collection; collection.reserve(491); @@ -43,7 +42,6 @@ TEMPLATE_TEST_CASE( "heap exhaustion for bidirectional probes", "[probe][heap_ex decltype(cppsort::probe::dis), decltype(cppsort::probe::mono), decltype(cppsort::probe::osc), - decltype(cppsort::probe::par), decltype(cppsort::probe::runs) ) { std::list collection; @@ -63,7 +61,6 @@ TEMPLATE_TEST_CASE( "heap exhaustion for forward probes", "[probe][heap_exhausti decltype(cppsort::probe::dis), decltype(cppsort::probe::mono), decltype(cppsort::probe::osc), - decltype(cppsort::probe::par), decltype(cppsort::probe::runs) ) { std::forward_list collection; diff --git a/testsuite/probes/every_probe_move_compare_projection.cpp b/testsuite/probes/every_probe_move_compare_projection.cpp index a1a80603..0549eb88 100644 --- a/testsuite/probes/every_probe_move_compare_projection.cpp +++ b/testsuite/probes/every_probe_move_compare_projection.cpp @@ -18,7 +18,6 @@ TEMPLATE_TEST_CASE( "every probe with comparison function altered by move", "[pr decltype(cppsort::probe::max), decltype(cppsort::probe::mono), decltype(cppsort::probe::osc), - decltype(cppsort::probe::par), decltype(cppsort::probe::rem), decltype(cppsort::probe::runs), decltype(cppsort::probe::sus) ) @@ -41,7 +40,6 @@ TEMPLATE_TEST_CASE( "every probe with projection function altered by move", "[pr decltype(cppsort::probe::max), decltype(cppsort::probe::mono), decltype(cppsort::probe::osc), - decltype(cppsort::probe::par), decltype(cppsort::probe::rem), decltype(cppsort::probe::runs), decltype(cppsort::probe::sus) ) diff --git a/testsuite/probes/par.cpp b/testsuite/probes/par.cpp deleted file mode 100644 index 98b6168b..00000000 --- a/testsuite/probes/par.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2016-2021 Morwenn - * SPDX-License-Identifier: MIT - */ -#include -#include -#include -#include -#include -#include - -TEST_CASE( "presortedness measure: par", "[probe][par]" ) -{ - using cppsort::probe::par; - - SECTION( "simple test" ) - { - const std::forward_list li = { 48, 43, 96, 44, 42, 34, 42, 57, 68, 69 }; - CHECK( par(li) == 7 ); - CHECK( par(li.begin(), li.end()) == 7 ); - - std::vector> tricky(li.begin(), li.end()); - CHECK( par(tricky, &internal_compare::compare_to) == 7 ); - } - - SECTION( "roughly sorting test" ) - { - // Example from *Roughly Sorting: Sequential and Parallel Approach* - // by T. Altman and Y. Igarashi - - const std::forward_list li = { - 2, 3, 5, 1, 4, 2, 6, - 8, 7, 9, 8, 11, 6, 13, - 12, 16, 15, 17, 18, - 20, 18, 19, 21, 19 - }; - CHECK( par(li) == 5 ); - } - - SECTION( "upper bound" ) - { - // The upper bound should correspond to the size of - // the input sequence minus one - - const std::forward_list li = { 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; - auto max_n = par.max_for_size(cppsort::utility::size(li)); - CHECK( max_n == 10 ); - CHECK( par(li) == max_n ); - CHECK( par(li.begin(), li.end()) == max_n ); - } -} diff --git a/testsuite/probes/relations.cpp b/testsuite/probes/relations.cpp index e5254d72..3e0c106f 100644 --- a/testsuite/probes/relations.cpp +++ b/testsuite/probes/relations.cpp @@ -23,6 +23,7 @@ TEST_CASE( "relations between measures of presortedness", "[probe]" ) // tests check that these relations are respected in // the library + auto dis = cppsort::probe::dis(sequence); auto enc = cppsort::probe::enc(sequence); auto exc = cppsort::probe::exc(sequence); auto ham = cppsort::probe::ham(sequence); @@ -30,7 +31,6 @@ TEST_CASE( "relations between measures of presortedness", "[probe]" ) auto max = cppsort::probe::max(sequence); auto mono = cppsort::probe::mono(sequence); auto osc = cppsort::probe::osc(sequence); - auto par = cppsort::probe::par(sequence); auto rem = cppsort::probe::rem(sequence); auto runs = cppsort::probe::runs(sequence); auto sus = cppsort::probe::sus(sequence); @@ -46,14 +46,14 @@ TEST_CASE( "relations between measures of presortedness", "[probe]" ) CHECK( exc + 1 <= ham ); CHECK( ham <= 2 * exc ); - CHECK( max <= par ); - CHECK( par <= 2 * max ); + CHECK( max <= dis ); + CHECK( dis <= 2 * max ); // A New Measure of Presortedness // by Vladimir Estivill-Castro and Derick Wood - CHECK( par <= inv ); - CHECK( rem <= size * (1 - 1 / (par + 1)) ); - CHECK( inv <= size * par / 2 ); + CHECK( dis <= inv ); + CHECK( rem <= size * (1 - 1 / (dis + 1)) ); + CHECK( inv <= size * dis / 2 ); // Encroaching lists as a measure of presortedness // by Steven S. Skiena @@ -72,7 +72,7 @@ TEST_CASE( "relations between measures of presortedness", "[probe]" ) // by Christos Levcopoulos and Ola Petersson CHECK( osc <= 4 * inv ); CHECK( osc <= 2 * size * runs + size ); - CHECK( osc <= size * par ); + CHECK( osc <= size * dis ); // Intuitive result: a descending run can be seen as several // ascending runs diff --git a/tools/mops-partial-ordering.tex b/tools/mops-partial-ordering.tex index 6aea5af3..7b485551 100644 --- a/tools/mops-partial-ordering.tex +++ b/tools/mops-partial-ordering.tex @@ -1,7 +1,7 @@ % Copyright (c) 2021 Morwenn % SPDX-License-Identifier: MIT -\documentclass{minimal} +\documentclass{standalone} \usepackage{bm, pgf, tikz} \usetikzlibrary{arrows, automata, backgrounds, positioning} @@ -37,7 +37,7 @@ \node[state] (inv) [below of=osc] {$\bm{Inv}~$$\equiv$$~DS$}; \node[state] (sus) [below of=enc] {$\bm{SUS}$}; \node[state] (exc) [below of=rem] {$\bm{Exc}~$$\equiv$$~\bm{Ham}$}; - \node[state] (max) [below of=inv] {$\bm{Max}~$$\equiv$$~\bm{Dis}~$$\equiv$$~\bm{Par}$}; + \node[state] (max) [below of=inv] {$\bm{Max}~$$\equiv$$~\bm{Dis}~$}; \node[state] (runs) [below of=sus] {$\bm{Runs}$}; \node[state] (m01) [below of=max] {$m_{01}$}; \node[state] (m0) [below of=m01] {$m_{0}$}; diff --git a/tools/test_failing_sorter.cpp b/tools/test_failing_sorter.cpp index 6b95237b..4ac84bc3 100644 --- a/tools/test_failing_sorter.cpp +++ b/tools/test_failing_sorter.cpp @@ -68,7 +68,6 @@ void test(const char* name) << "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 << "sus: " << cppsort::probe::sus(copy2) << std::endl From 593d6a0bf066c52f0f09f784c0443421ef353506 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 31 Jul 2021 17:31:03 +0200 Subject: [PATCH 11/79] More/better documentation for MOPs --- docs/Measures-of-presortedness.md | 42 ++++++++++++++++++++++++------- docs/Original-research.md | 4 +-- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/docs/Measures-of-presortedness.md b/docs/Measures-of-presortedness.md index 4c4fa47e..60c5d5f5 100644 --- a/docs/Measures-of-presortedness.md +++ b/docs/Measures-of-presortedness.md @@ -4,7 +4,7 @@ Given a measure of presortedness *M*, a comparison sort is said to be *M*-optima ## Formal definition -Measures of presortedness were formally defined by Manilla in *Measures of presortedness and optimal sorting algorithms*. Here is the formal definition as reformulated by La rocca & Cantone in [*NeatSort - A practical adaptive algorithm*][neatsort]: +Measures of presortedness were formally defined by H. Mannila in *Measures of presortedness and optimal sorting algorithms*. Here is the formal definition as reformulated by M. La Rocca & D. Cantone in [*NeatSort - A practical adaptive algorithm*][neatsort]: > Given two sequences *X* and *Y* of distinct elements, a measure of disorder *M* is a function that satisfies the following properties: > @@ -18,7 +18,7 @@ A few measures of presortedness described in the research papers actually return ### Partial ordering of measures of presortedness -La rocca & Cantone also define a partial order on measure of presortedness as follows: +La Rocca & Cantone also define a partial order on measures of presortedness as follows: Let *M1* and *M2* be two measures of presortedness. - *M1* is algorithmically finer than *M2* if and only if any *M1*-optimal algorithm is also *M2*-optimal. @@ -31,7 +31,7 @@ The graph below shows the partial ordering of several measures of presortedness: ![Partial ordering of measures of presortedness](https://github.com/Morwenn/cpp-sort/wiki/images/mops-partial-ordering.png) -This graph is more complete version of the one in *A framework for adaptive sorting* by O. Petersson and A. Moffat. The *Max* ≡ *Dis* equivalence comes from [*NeatSort - A practical adaptive algorithm*][neatsort] by La rocca & Cantone. The relations of *Mono* are empirically derived [original research][original-research]. +This graph is a modified version of the one in *A framework for adaptive sorting* by O. Petersson and A. Moffat. The relations of *Mono* are empirically derived [original research][original-research] and incomplete (unknown relations with *Max*, *Osc* and *SUS*). The measures of presortedness in bold in the graph are available in **cpp-sort**, the others are not. @@ -112,7 +112,7 @@ When enough memory is available, `probe::dis` runs in O(n), otherwise it falls b #include ``` -Computes the number of encroaching lists that can be extracted from *X* minus one (see Skiena's *Encroaching lists as a measure of presortedness*). +Computes the number of encroaching lists that can be extracted from *X* minus one (see *Encroaching lists as a measure of presortedness* by S. Skiena). | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | @@ -186,11 +186,11 @@ Computes the maximum distance an element in *X* must travel to find its sorted p #include ``` -Computes the number of non-increasing and non-decreasing runs in *X* minus one. +Computes the number of non-increasing and non-decreasing consecutive runs of adjacent elements that need to be removed from *X* to make it sorted The measure of presortedness is slightly different from its original description in [*Sort Race*][sort-race] by H. Zhang, B. Meng and Y. Liang: -* It subtracts 1 from the number of runs, thus returning 0 when *X* is sorted -* It explicitly handles non-increasing and non-decreasing runs, not only the strictly increasing or decreasing ones +* It subtracts 1 from the number of runs, thus returning 0 when *X* is sorted. +* It explicitly handles non-increasing and non-decreasing runs, not only the strictly increasing or decreasing ones. | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | @@ -206,7 +206,7 @@ The measure of presortedness is slightly different from its original description #include ``` -Computes the *Oscillation* measure described by Levcopoulos and Petersson in *Adaptive Heapsort*. +Computes the *Oscillation* measure described by C. Levcopoulos and O. Petersson in *Adaptive Heapsort*. | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | @@ -258,7 +258,7 @@ Computes the number of non-decreasing runs in *X* minus one. Computes the minimum number of non-decreasing subsequences (of possibly not adjacent elements) into which *X* can be partitioned. It happens to correspond to the size of the [longest decreasing subsequence][longest-increasing-subsequence] of *X*. -*SUS* stands for *Shuffled Up-Sequences* and was introduced in *Sorting Shuffled Monotone Sequences* by Levcopoulos and Petersson. +*SUS* stands for *Shuffled Up-Sequences* and was introduced in *Sorting Shuffled Monotone Sequences* by C. Levcopoulos and O. Petersson. | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | @@ -268,6 +268,30 @@ Computes the minimum number of non-decreasing subsequences (of possibly not adja *New in version 1.10.0* +## Other measures of presortedness + +Some additional measures of presortedness how been described in the literature but do not appear in the partial ordering graph. This section describes some of them but is not an exhaustive list. + +### *Par* + +*Par* was described by V. Estivill-Castro and D. Wood in *A New Measure of Presortedness* as follows: + +> *Par(X)* = min { *p* | *X* is *p*-sorted } + +The following definition is also given to determine whether a sequence is *p*-sorted: + +> *X* is *p*-sorted iff for all *i*, *j* ∈ {1, 2, ..., |*X*|}, *i* - *j* > *p* implies *Xj* ≤ *Xi*. + +*Right invariant metrics and measures of presortedness* by V. Estivill-Castro, H. Mannila and D. Wood mentions that: + +> In fact, *Par*(*X*) = *Dis*(*X*), for all *X*. + +In their subsequent papers, those authors consistently use *Dis* instead of *Par*, often accompanied by a link to *A New Measure of Presortedness*. + +### *Radius* + +T. Altman and Y. Igarashi mention the concept of *k*-sortedness and the measure *Radius*(*X*) in *Roughly Sorting: Sequential and Parallel Approach*. However *k*-sortedness is the same as *p*-sortedness, and *Radius* is just another name for *Par* (and thus for *Dis*). + [hamming-distance]: https://en.wikipedia.org/wiki/Hamming_distance [longest-increasing-subsequence]: https://en.wikipedia.org/wiki/Longest_increasing_subsequence diff --git a/docs/Original-research.md b/docs/Original-research.md index 73aa233f..59495895 100644 --- a/docs/Original-research.md +++ b/docs/Original-research.md @@ -205,14 +205,14 @@ The measure of presortedness *Mono* is described in [*Sort Race*][sort-race] by > Intuitively, if *Mono*(*X*) = *k*, then *X* is the concatenation of *k* monotonic lists (either sorted or reversely sorted). -It counts the number of ascending or descending runs in *X*. Technically this definition in the paper makes it return 1 when the *X* is sorted, which goes against the original definition of a measure of presortedness by Manilla, which starts with the following condition: +It computes the number of ascending or descending runs in *X*. Technically the definition in the paper makes it return 1 when the *X* is sorted, which goes against the original definition of a measure of presortedness by Mannila, which starts with the following condition: > If *X* is sorted, then *M*(*X*) = 0 Therefore we redefine *Mono*(*X*) as the number of non-increasing and non-decreasing consecutive runs of adjacent elements that need to be removed from *X* to make it sorted. - ***Mono* ⊇ *Runs***: this relation is already mentioned in *Sort Race* and rather intuitive: since *Mono* detects both non-increasing and non-decreasing runs, it is as least as good as *Runs* that only detects non-decreasing runs. - ***SMS* ⊇ *Mono***: this one seems intuitive too: *SMS* which removes runs of non-adjacent elements should be at least as good as *Mono* which only removes runs of adjacent elements. -- ***Enc* ⊇ *Mono***: when making encroaching lists, *Enc* is guaranteed to not create no more than one such new list per non-increasing or non-decreasing runs, so the result will be at most as big as that of *Mono*. However *Enc* can also find presortedness in patterns such as {5, 6, 4, 7, 3, 8, 2, 9, 1, 10} where *Mono* will find maximum disorder. Therefore *Enc* is strictly better than *Mono*. +- ***Enc* ⊇ *Mono***: when making encroaching lists, *Enc* is guaranteed to create no more than one such new list per non-increasing or non-decreasing run found in *X*, so the result will be at most as big as that of *Mono*. However *Enc* can also find presortedness in patterns such as {5, 6, 4, 7, 3, 8, 2, 9, 1, 10} where *Mono* will find maximum disorder. Therefore *Enc*(*X*) should always be at most as big as *Mono*(*X*). The following relations can be transitively deduced from the results presented in *A framework for adaptive sorting*: - ***Mono* ⊋ *Exc***: we know that *SMS* ⊇ *Mono* and *SMS* ⊋ *Exc* From f9e440e6815c516892a6f8c250d54d363b0e374f Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 31 Jul 2021 21:08:23 +0200 Subject: [PATCH 12/79] Add new MOP relation test --- testsuite/probes/relations.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/testsuite/probes/relations.cpp b/testsuite/probes/relations.cpp index 3e0c106f..0b6303cb 100644 --- a/testsuite/probes/relations.cpp +++ b/testsuite/probes/relations.cpp @@ -55,6 +55,10 @@ TEST_CASE( "relations between measures of presortedness", "[probe]" ) CHECK( rem <= size * (1 - 1 / (dis + 1)) ); CHECK( inv <= size * dis / 2 ); + // Practical Adaptive Sorting + // by Vladimir Estivill-Castro and Derick Wood + CHECK( rem <= 2 * exc ); + // Encroaching lists as a measure of presortedness // by Steven S. Skiena CHECK( enc <= runs ); From a100735ec3147ee934585dc0835099af48332b71 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 1 Aug 2021 14:42:41 +0200 Subject: [PATCH 13/79] Remove array RL in probe::dis The array wasn't needed because the cumulative min can be computed oon the fly during the computation of DM. This new version computes LR then goes straight to a fused RL/DM algorithm without extra space. The new algorithm was more than twice as fast than the old one in benchmarks. --- include/cpp-sort/probes/dis.h | 49 +++++++++++++++++------------------ 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/include/cpp-sort/probes/dis.h b/include/cpp-sort/probes/dis.h index a0ff8845..22c6e5d5 100644 --- a/include/cpp-sort/probes/dis.h +++ b/include/cpp-sort/probes/dis.h @@ -65,46 +65,45 @@ namespace probe auto&& comp = utility::as_function(compare); auto&& proj = utility::as_function(projection); - // Algorithm described in *Roughly Sorting: Sequential and Parallel Approach* - // by T. Altman and Y. Igarashi + // Space-optimized version of the algorithm described in *Roughly Sorting: + // Sequential and Parallel Approach* by T. Altman and Y. Igarashi if (size < 2) { return 0; } - // Algorithm LR - cppsort::detail::immovable_vector b(size); - b.emplace_back(first); + // Algorithm LR: cumulative max from left to right + cppsort::detail::immovable_vector lr_cummax(size); + lr_cummax.emplace_back(first); for (auto it = std::next(first) ; it != last ; ++it) { - if (comp(proj(*b.back()), proj(*it))) { - b.emplace_back(it); + if (comp(proj(*lr_cummax.back()), proj(*it))) { + lr_cummax.emplace_back(it); } else { - b.emplace_back(b.back()); + lr_cummax.emplace_back(lr_cummax.back()); } } - // Algorithm RL - cppsort::detail::immovable_vector c(size); - c.emplace_back(std::prev(last)); - auto rfirst = std::make_reverse_iterator(last); - auto rlast = std::make_reverse_iterator(first); - for (auto it = std::next(rfirst) ; it != rlast ; ++it) { - if (comp(proj(*it), proj(*c.back()))) { - c.emplace_back(std::prev(it.base())); - } else { - c.emplace_back(c.back()); - } - } - std::reverse(c.begin(), c.end()); - - // Algorithm DM, without extra storage + // Merged algorithms without extra storage: + // - RL: cumulative min from right to left + // - DM: max distance of an inversion difference_type res = 0; difference_type i = size; + auto rl_it = std::prev(last); // Iterator to the current RL element + auto rl_min_it = rl_it; // Iterator to the current minimum of RL for (auto j = i ; j > 0 ; --j) { - while (j <= i && i >= 1 && not comp(proj(*b[j - 1]), proj(*c[i - 1])) - && (j == 1 || not comp(proj(*c[i - 1]), proj(*b[j - 2])))) { + while (j <= i && not comp(proj(*lr_cummax[j - 1]), proj(*rl_min_it)) + && (j == 1 || not comp(proj(*rl_min_it), proj(*lr_cummax[j - 2])))) { + // Compute the next value of DM res = std::max(res, i - j); + // Compute the next value of RL --i; + if (i == 0) { + return res; + } + --rl_it; + if (comp(proj(*rl_it), proj(*rl_min_it))) { + rl_min_it = rl_it; + } } } From bd312942786cc5210a5af73997cbd0dba78a005d Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 2 Aug 2021 16:29:09 +0200 Subject: [PATCH 14/79] Micro-optimization for probe::dis The value of Osc(X) is obtained by computing j - i, and can only grow through the course of the algorithm. Since i always decreases, if it becomes smaller or equal to the current value of Osc(X), then it means that we're done because we can't find a bigger Osc(X) than the current value. --- include/cpp-sort/probes/dis.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/cpp-sort/probes/dis.h b/include/cpp-sort/probes/dis.h index 22c6e5d5..59cd1d47 100644 --- a/include/cpp-sort/probes/dis.h +++ b/include/cpp-sort/probes/dis.h @@ -96,8 +96,7 @@ namespace probe // Compute the next value of DM res = std::max(res, i - j); // Compute the next value of RL - --i; - if (i == 0) { + if (--i <= res) { return res; } --rl_it; From aa44b38ddd62d74a23637a33afc8084015361ec5 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 2 Aug 2021 17:26:11 +0200 Subject: [PATCH 15/79] Fix bug in probe::osc The implementation called std::min and std::max on projected values without passing the comparators to them, leading compile-time errors or runtime errors depending on whether the results of the projected values were comparable, and whether it compared them correctly. --- include/cpp-sort/probes/osc.h | 4 ++-- testsuite/probes/osc.cpp | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/include/cpp-sort/probes/osc.h b/include/cpp-sort/probes/osc.h index 78107d9e..a63fd66c 100644 --- a/include/cpp-sort/probes/osc.h +++ b/include/cpp-sort/probes/osc.h @@ -58,8 +58,8 @@ namespace probe while (next != last) { - if (comp((std::min)(proj(*current), proj(*next)), value) && - comp(value, (std::max)(proj(*current), proj(*next)))) + if (comp((std::min)(proj(*current), proj(*next), comp), value) && + comp(value, (std::max)(proj(*current), proj(*next), comp))) { ++count; } diff --git a/testsuite/probes/osc.cpp b/testsuite/probes/osc.cpp index 7a173361..bceb0e25 100644 --- a/testsuite/probes/osc.cpp +++ b/testsuite/probes/osc.cpp @@ -8,6 +8,7 @@ #include #include #include +#include TEST_CASE( "presortedness measure: osc", "[probe][osc]" ) { @@ -38,4 +39,14 @@ TEST_CASE( "presortedness measure: osc", "[probe][osc]" ) CHECK( osc(li) == max_n ); CHECK( osc(li.begin(), li.end()) == max_n ); } + + SECTION( "regressions" ) + { + using wrapper = generic_wrapper>; + std::vector vec = { {{6}}, {{3}}, {{9}}, {{8}}, {{4}}, {{7}}, {{1}}, {{11}} }; + auto comp = [](generic_wrapper const& lhs, generic_wrapper const& rhs) { + return lhs.value < rhs.value; + }; + CHECK( osc(vec, comp, &wrapper::value) == 17 ); + } } From 77c4c756d1cff0ce3f2566b4a942fa10fbad9270 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 3 Aug 2021 12:17:33 +0200 Subject: [PATCH 16/79] New O(n log n) implementation for probe::osc Thanks to Control from The Studio for the algorithm. The old one is kept for now to avoid breaking the case where not enough memory is available, but it is deprecated and will be removed in version 2.0.0. --- docs/Measures-of-presortedness.md | 7 ++ include/cpp-sort/probes/osc.h | 198 +++++++++++++++++++++++++----- 2 files changed, 174 insertions(+), 31 deletions(-) diff --git a/docs/Measures-of-presortedness.md b/docs/Measures-of-presortedness.md index 60c5d5f5..23d37a65 100644 --- a/docs/Measures-of-presortedness.md +++ b/docs/Measures-of-presortedness.md @@ -210,10 +210,17 @@ Computes the *Oscillation* measure described by C. Levcopoulos and O. Petersson | Complexity | Memory | Iterators | | ----------- | ----------- | ------------- | +| n log n | n | Forward | | n² | 1 | Forward | +When there isn't enough extra memory available, `probe::osc` falls back to an in-place O(n²) algorithm. + `max_for_size`: (|*X*| * (|*X*| - 2) - 1) / 2 when the values in *X* are strongly oscillating. +***WARNING:** the O(n²) fallback of `probe::osc` is deprecated since version 1.12.0 and removed in version 2.0.0.* + +*Changed in version 1.12.0:* `probe::osc` is now O(n log n) instead of O(n²) but now also requires O(n) memory. The O(n²) is kept for backward compatibility but will be removed in the future. + ### *Par* ```cpp diff --git a/include/cpp-sort/probes/osc.h b/include/cpp-sort/probes/osc.h index a63fd66c..8c94dc40 100644 --- a/include/cpp-sort/probes/osc.h +++ b/include/cpp-sort/probes/osc.h @@ -11,13 +11,22 @@ #include #include #include +#include +#include #include +#include +#include #include #include #include #include +#include #include +#include "../detail/equal_range.h" +#include "../detail/functional.h" +#include "../detail/immovable_vector.h" #include "../detail/iterator_traits.h" +#include "../detail/pdqsort.h" namespace cppsort { @@ -25,8 +34,163 @@ namespace probe { namespace detail { + template + auto inplace_osc_algo(ForwardIterator first, ForwardIterator last, + cppsort::detail::difference_type_t size, + Compare compare, Projection projection) + -> ::cppsort::detail::difference_type_t + { + // Deprecated in-place O(n^2) algorithm + + using difference_type = cppsort::detail::difference_type_t; + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + if (size < 2) { + return 0; + } + + difference_type count = 0; + for (auto it = first; it != last; ++it) { + auto&& value = proj(*it); + + auto current = first; + auto next = std::next(first); + + while (next != last) { + if (comp((std::min)(proj(*current), proj(*next), comp), value) && + comp(value, (std::max)(proj(*current), proj(*next), comp))) { + ++count; + } + ++current; + ++next; + } + } + return count; + } + + template + auto allocating_osc_algo(ForwardIterator first, ForwardIterator last, + cppsort::detail::difference_type_t size, + Compare compare, Projection projection) + -> ::cppsort::detail::difference_type_t + { + using difference_type = ::cppsort::detail::difference_type_t; + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + if (size < 2) { + return 0; + } + + //////////////////////////////////////////////////////////// + // Indirectly sort the iterators + + // Copy the iterators in a vector + cppsort::detail::immovable_vector iterators(size); + for (auto it = first; it != last; ++it) { + iterators.emplace_back(it); + } + + // Sort the iterators on pointed values + cppsort::detail::pdqsort( + iterators.begin(), iterators.end(), compare, + cppsort::detail::indirect(projection) + ); + + //////////////////////////////////////////////////////////// + // Compute the oscillation + + // This algorithm was proposed by Control and works as follow: + // initialize a vector of N 0, then for each pair of elements + // in the collection (ex: (a,b), (b,c), (c,d), etc.), find the + // position of the elements of the pair in the sorted collection + // (pos_min and pos_max), increment cross[pos_min + 1] and + // decrement cross[pos_max - 1]. Then compute the prefix sum of + // cross, the oscillation is the sum of that prefix sum. + + // Note: in the following algorithm, all upper_bound calls are + // performed on the full iterator sequence without taking + // advantage of the information we already have to reduce + // the search space. This is because benchmarks reported + // that reducing the search space made the algorithm up to + // twice as slow. Comments in the code contain the lines + // required to reduce the search space again. + + std::vector cross(size, 0); + + auto prev_bounds = cppsort::detail::equal_range( + iterators.begin(), iterators.end(), proj(*first), + compare, cppsort::detail::indirect(projection) + ); + + for (auto prev = first, current = std::next(prev); current != last; ++prev, (void)++current) { + difference_type min_idx, max_idx; + if (comp(proj(*prev), proj(*current))) { + auto current_bounds = cppsort::detail::equal_range( + //prev_bounds.second, std::prev(iterators.end()), proj(*current), + iterators.begin(), iterators.end(), proj(*current), + compare, cppsort::detail::indirect(projection) + ); + min_idx = prev_bounds.second - iterators.begin(); + max_idx = current_bounds.first - iterators.begin(); + prev_bounds = current_bounds; + } else if (comp(proj(*current), proj(*prev))) { + auto current_bounds = cppsort::detail::equal_range( + //iterators.begin(), prev_bounds.first, proj(*current), + iterators.begin(), iterators.end(), proj(*current), + compare, cppsort::detail::indirect(projection) + ); + min_idx = current_bounds.second - iterators.begin(); + max_idx = prev_bounds.first - iterators.begin(); + prev_bounds = current_bounds; + } else { + // *prev == *current, bounds don't change + continue; + } + + cross[min_idx] += 1; + cross[max_idx] -= 1; + } + + std::partial_sum(cross.begin(), cross.end(), cross.begin()); + return std::accumulate(cross.begin(), cross.end(), difference_type(0)); + } + + template + auto osc_algo(ForwardIterator first, ForwardIterator last, + cppsort::detail::difference_type_t size, + Compare compare, Projection projection) + -> ::cppsort::detail::difference_type_t + { + try { + return allocating_osc_algo(first, last, size, compare, projection); + } catch (std::bad_alloc&) { + return inplace_osc_algo( + first, last, size, + std::move(compare), std::move(projection) + ); + } + } + struct osc_impl { + template< + typename ForwardIterable, + typename Compare = std::less<>, + typename Projection = utility::identity, + typename = std::enable_if_t< + is_projection_v + > + > + auto operator()(ForwardIterable&& iterable, Compare compare={}, Projection projection={}) const + -> decltype(auto) + { + return osc_algo(std::begin(iterable), std::end(iterable), + utility::size(iterable), + std::move(compare), std::move(projection)); + } + template< typename ForwardIterator, typename Compare = std::less<>, @@ -37,38 +201,10 @@ namespace probe > auto operator()(ForwardIterator first, ForwardIterator last, Compare compare={}, Projection projection={}) const - -> cppsort::detail::difference_type_t + -> decltype(auto) { - using difference_type = cppsort::detail::difference_type_t; - auto&& comp = utility::as_function(compare); - auto&& proj = utility::as_function(projection); - - if (first == last || std::next(first) == last) - { - return 0; - } - - difference_type count = 0; - for (auto it = first ; it != last ; ++it) - { - auto&& value = proj(*it); - - auto current = first; - auto next = std::next(first); - - while (next != last) - { - if (comp((std::min)(proj(*current), proj(*next), comp), value) && - comp(value, (std::max)(proj(*current), proj(*next), comp))) - { - ++count; - } - - ++current; - ++next; - } - } - return count; + return osc_algo(first, last, std::distance(first, last), + std::move(compare), std::move(projection)); } template From b4f531eeff4567789c7992b72afa2f8eb09c658f Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 3 Aug 2021 13:35:15 +0200 Subject: [PATCH 17/79] probe::dis: cosmetic change --- include/cpp-sort/probes/dis.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/include/cpp-sort/probes/dis.h b/include/cpp-sort/probes/dis.h index 59cd1d47..418753f1 100644 --- a/include/cpp-sort/probes/dis.h +++ b/include/cpp-sort/probes/dis.h @@ -22,6 +22,7 @@ #include "../detail/immovable_vector.h" #include "../detail/is_p_sorted.h" #include "../detail/iterator_traits.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -29,11 +30,11 @@ namespace probe { namespace detail { - template - auto inplace_dis_probe_algo(RandomAccessIterator first, RandomAccessIterator last, - cppsort::detail::difference_type_t size, + template + auto inplace_dis_probe_algo(ForwardIterator first, ForwardIterator last, + cppsort::detail::difference_type_t size, Compare compare, Projection projection) - -> ::cppsort::detail::difference_type_t + -> ::cppsort::detail::difference_type_t { // Simple algorithm in O(n log n) time and O(1) space From 46ffc070adefc25d33c0d9b9d6e31116616b0808 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 3 Aug 2021 19:09:40 +0200 Subject: [PATCH 18/79] Add another MOPs relation to the test suite --- testsuite/probes/relations.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/testsuite/probes/relations.cpp b/testsuite/probes/relations.cpp index 0b6303cb..29e47f8f 100644 --- a/testsuite/probes/relations.cpp +++ b/testsuite/probes/relations.cpp @@ -78,6 +78,10 @@ TEST_CASE( "relations between measures of presortedness", "[probe]" ) CHECK( osc <= 2 * size * runs + size ); CHECK( osc <= size * dis ); + // Computing and ranking measures of presortedness + // by Jingsen Chen + CHECK( enc <= dis + 1 ); + // Intuitive result: a descending run can be seen as several // ascending runs CHECK( mono <= runs ); From 2cf80ba9dce5bab90679b17a3947519338ebe425 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 3 Aug 2021 19:56:06 +0200 Subject: [PATCH 19/79] New measure of presortedness: block --- docs/Measures-of-presortedness.md | 20 +++ docs/images/mops-partial-ordering.png | Bin 29045 -> 29099 bytes include/cpp-sort/probes.h | 1 + include/cpp-sort/probes/block.h | 136 ++++++++++++++++++ testsuite/CMakeLists.txt | 1 + testsuite/probes/block.cpp | 39 +++++ testsuite/probes/every_probe_common.cpp | 2 + .../every_probe_move_compare_projection.cpp | 2 + testsuite/probes/relations.cpp | 5 + tools/mops-partial-ordering.tex | 2 +- tools/test_failing_sorter.cpp | 1 + 11 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 include/cpp-sort/probes/block.h create mode 100644 testsuite/probes/block.cpp diff --git a/docs/Measures-of-presortedness.md b/docs/Measures-of-presortedness.md index 23d37a65..9f275b59 100644 --- a/docs/Measures-of-presortedness.md +++ b/docs/Measures-of-presortedness.md @@ -85,6 +85,26 @@ Measures of presortedness are pretty formalized, so the names of the functions i In the following descriptions we use *X* to represent the input sequence, and |*X*| to represent the size of that sequence. +### *Block* + +```cpp +#include +``` + +Computes the number of elements in a sequence that aren't followed by the same element in the sorted sequence. + +Our implementation is slightly different from the original description in *Sublinear merging and natural mergesort* by S. Carlsson, C. Levcopoulos and O. Petersson: +* It doesn't add 1 to the general result, thus returning 0 when *X* is sorted - therefore respecting the Mannila definition of a MOP. +* It explicitly handles elements that compare equivalent, while the original formal definition makes it difficult. + +| Complexity | Memory | Iterators | +| ----------- | ----------- | ------------- | +| n log n | n | Forward | + +`max_for_size`: |*X*| - 1 when *X* is sorted in reverse order. + +*New in version 1.12.0* + ### *Dis* ```cpp diff --git a/docs/images/mops-partial-ordering.png b/docs/images/mops-partial-ordering.png index d8f865c0ce7dc0912eeaffc055a1ca361737a947..e845259ef37f995bde495b6b7c82083524f8a3d7 100644 GIT binary patch literal 29099 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≫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 literal 29045 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 gSmCC({_!;5&urFY+QcPuj-n!P#w=~6X71Ym0 #include #include #include diff --git a/include/cpp-sort/probes/block.h b/include/cpp-sort/probes/block.h new file mode 100644 index 00000000..442fcdca --- /dev/null +++ b/include/cpp-sort/probes/block.h @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2021 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_PROBES_BLOCK_H_ +#define CPPSORT_PROBES_BLOCK_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../detail/functional.h" +#include "../detail/immovable_vector.h" +#include "../detail/iterator_traits.h" +#include "../detail/pdqsort.h" + +namespace cppsort +{ +namespace probe +{ + namespace detail + { + template + auto block_probe_algo(ForwardIterator first, ForwardIterator last, + cppsort::detail::difference_type_t size, + Compare compare, Projection projection) + -> ::cppsort::detail::difference_type_t + { + using difference_type = ::cppsort::detail::difference_type_t; + auto&& comp = utility::as_function(compare); + auto&& proj = utility::as_function(projection); + + if (size < 2) { + return 0; + } + + //////////////////////////////////////////////////////////// + // Indirectly sort the iterators + + // Copy the iterators in a vector + cppsort::detail::immovable_vector iterators(size); + for (auto it = first; it != last; ++it) { + iterators.emplace_back(it); + } + + // Sort the iterators on pointed values + cppsort::detail::pdqsort( + iterators.begin(), iterators.end(), compare, + cppsort::detail::indirect(projection) + ); + + //////////////////////////////////////////////////////////// + // Count the number of consecutive pairs in the original + // collection that can't be found in the sorted one + + difference_type count = 0; + auto it_last_1 = std::prev(iterators.end()); + for (auto it = iterators.begin(); it != it_last_1; ++it) { + auto orig_next = std::next(*it); + if (orig_next == last) { + if (comp(proj(**it), proj(**it_last_1)) || comp(proj(**it_last_1), proj(**it))) { + ++count; + } + continue; + } + auto sorted_next = *std::next(it); + if (comp(proj(*orig_next), proj(*sorted_next)) || comp(proj(*sorted_next), proj(*orig_next))) { + ++count; + } + } + return count; + } + + struct block_impl + { + template< + typename ForwardIterable, + typename Compare = std::less<>, + typename Projection = utility::identity, + typename = std::enable_if_t< + is_projection_v + > + > + auto operator()(ForwardIterable&& iterable, + Compare compare={}, Projection projection={}) const + -> decltype(auto) + { + return block_probe_algo(std::begin(iterable), std::end(iterable), + utility::size(iterable), + std::move(compare), std::move(projection)); + } + + template< + typename ForwardIterator, + typename Compare = std::less<>, + typename Projection = utility::identity, + typename = std::enable_if_t< + is_projection_iterator_v + > + > + auto operator()(ForwardIterator first, ForwardIterator last, + Compare compare={}, Projection projection={}) const + -> decltype(auto) + { + return block_probe_algo(first, last, std::distance(first, last), + std::move(compare), std::move(projection)); + } + + template + static constexpr auto max_for_size(Integer n) + -> Integer + { + return n == 0 ? 0 : n - 1; + } + }; + } + + namespace + { + constexpr auto&& block = utility::static_const< + sorter_facade + >::value; + } +}} + +#endif // CPPSORT_PROBES_BLOCK_H_ diff --git a/testsuite/CMakeLists.txt b/testsuite/CMakeLists.txt index b6e6501f..4c935a9b 100644 --- a/testsuite/CMakeLists.txt +++ b/testsuite/CMakeLists.txt @@ -176,6 +176,7 @@ add_executable(main-tests distributions/shuffled_16_values.cpp # Probes tests + probes/block.cpp probes/dis.cpp probes/enc.cpp probes/exc.cpp diff --git a/testsuite/probes/block.cpp b/testsuite/probes/block.cpp new file mode 100644 index 00000000..db1f0db9 --- /dev/null +++ b/testsuite/probes/block.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021 Morwenn + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include +#include + +TEST_CASE( "presortedness measure: block", "[probe][block]" ) +{ + using cppsort::probe::block; + + SECTION( "simple test" ) + { + std::forward_list li = { 74, 59, 62, 23, 86, 69, 18, 52, 77, 68 }; + CHECK( block(li) == 8 ); + CHECK( block(li.begin(), li.end()) == 8 ); + + std::vector> tricky(li.begin(), li.end()); + CHECK( block(tricky, &internal_compare::compare_to) == 8 ); + } + + SECTION( "upper bound" ) + { + // The upper bound should correspond to the size of + // the input sequence minus one + + std::forward_list li = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; + auto max_n = block.max_for_size(cppsort::utility::size(li)); + CHECK( max_n == 10 ); + CHECK( block(li) == max_n ); + CHECK( block(li.begin(), li.end()) == max_n ); + } +} diff --git a/testsuite/probes/every_probe_common.cpp b/testsuite/probes/every_probe_common.cpp index 5a048118..3aacd5b7 100644 --- a/testsuite/probes/every_probe_common.cpp +++ b/testsuite/probes/every_probe_common.cpp @@ -13,6 +13,7 @@ // TEMPLATE_TEST_CASE( "test every probe with all_equal distribution", "[probe]", + decltype(cppsort::probe::block), decltype(cppsort::probe::dis), decltype(cppsort::probe::enc), decltype(cppsort::probe::exc), @@ -35,6 +36,7 @@ TEMPLATE_TEST_CASE( "test every probe with all_equal distribution", "[probe]", } TEMPLATE_TEST_CASE( "test every probe with a sorted collection", "[probe]", + decltype(cppsort::probe::block), decltype(cppsort::probe::dis), decltype(cppsort::probe::enc), decltype(cppsort::probe::exc), diff --git a/testsuite/probes/every_probe_move_compare_projection.cpp b/testsuite/probes/every_probe_move_compare_projection.cpp index 0549eb88..1708ee2a 100644 --- a/testsuite/probes/every_probe_move_compare_projection.cpp +++ b/testsuite/probes/every_probe_move_compare_projection.cpp @@ -10,6 +10,7 @@ #include TEMPLATE_TEST_CASE( "every probe with comparison function altered by move", "[probe]", + decltype(cppsort::probe::block), decltype(cppsort::probe::dis), decltype(cppsort::probe::enc), decltype(cppsort::probe::exc), @@ -32,6 +33,7 @@ TEMPLATE_TEST_CASE( "every probe with comparison function altered by move", "[pr } TEMPLATE_TEST_CASE( "every probe with projection function altered by move", "[probe]", + decltype(cppsort::probe::block), decltype(cppsort::probe::dis), decltype(cppsort::probe::enc), decltype(cppsort::probe::exc), diff --git a/testsuite/probes/relations.cpp b/testsuite/probes/relations.cpp index 29e47f8f..993e9dbd 100644 --- a/testsuite/probes/relations.cpp +++ b/testsuite/probes/relations.cpp @@ -23,6 +23,7 @@ TEST_CASE( "relations between measures of presortedness", "[probe]" ) // tests check that these relations are respected in // the library + auto block = cppsort::probe::block(sequence); auto dis = cppsort::probe::dis(sequence); auto enc = cppsort::probe::enc(sequence); auto exc = cppsort::probe::exc(sequence); @@ -78,6 +79,10 @@ TEST_CASE( "relations between measures of presortedness", "[probe]" ) CHECK( osc <= 2 * size * runs + size ); CHECK( osc <= size * dis ); + // Sublinear Merging and Natural Mergesort + // by Svante Carlsson, Christos Levcopoulos and Ola Petersson + CHECK( block <= 3 * rem ); + // Computing and ranking measures of presortedness // by Jingsen Chen CHECK( enc <= dis + 1 ); diff --git a/tools/mops-partial-ordering.tex b/tools/mops-partial-ordering.tex index 7b485551..9a61bbef 100644 --- a/tools/mops-partial-ordering.tex +++ b/tools/mops-partial-ordering.tex @@ -30,7 +30,7 @@ \node[state] (loc) [below of=reg] {$Loc$}; \node[state] (hist) [left=2.2cm of loc] {$Hist$}; \node[state] (sms) [right=2.2cm of loc] {$SMS$}; - \node[state] (block) [below of=hist] {$Block$}; + \node[state] (block) [below of=hist] {$\bm{Block}$}; \node[state] (osc) [below of=loc] {$\bm{Osc}$}; \node[state] (enc) [below of=sms] {$\bm{Enc}$}; \node[state] (rem) [below of=block] {$\bm{Rem}$}; diff --git a/tools/test_failing_sorter.cpp b/tools/test_failing_sorter.cpp index 4ac84bc3..24b16010 100644 --- a/tools/test_failing_sorter.cpp +++ b/tools/test_failing_sorter.cpp @@ -60,6 +60,7 @@ void test(const char* name) // Measures of presortedness std::cout << '\n' + << "block: " << cppsort::probe::block(copy2) << std::endl << "dis: " << cppsort::probe::dis(copy2) << std::endl << "enc: " << cppsort::probe::enc(copy2) << std::endl << "exc: " << cppsort::probe::exc(copy2) << std::endl From 6e8ebac38f0d52bbd8a119acdecc3e020697229e Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 5 Sep 2021 23:51:32 +0200 Subject: [PATCH 20/79] Replace std::enable_if_t with a custom implementation The new implementation reduces the number of class template instantiations to 2 and reduces a bit the size of the binary in debug mode. --- .../adapters/container_aware_adapter.h | 22 ++--- include/cpp-sort/adapters/counting_adapter.h | 9 +- include/cpp-sort/adapters/hybrid_adapter.h | 8 +- include/cpp-sort/adapters/indirect_adapter.h | 4 +- include/cpp-sort/adapters/schwartz_adapter.h | 8 +- include/cpp-sort/adapters/self_sort_adapter.h | 8 +- .../cpp-sort/adapters/small_array_adapter.h | 6 +- include/cpp-sort/adapters/stable_adapter.h | 8 +- include/cpp-sort/adapters/verge_adapter.h | 5 +- .../comparators/case_insensitive_less.h | 16 ++-- .../cpp-sort/comparators/partial_greater.h | 5 +- include/cpp-sort/comparators/partial_less.h | 5 +- include/cpp-sort/comparators/total_greater.h | 7 +- include/cpp-sort/comparators/total_less.h | 7 +- include/cpp-sort/comparators/weak_greater.h | 5 +- include/cpp-sort/comparators/weak_less.h | 5 +- include/cpp-sort/detail/bitops.h | 5 +- .../detail/container_aware/insertion_sort.h | 14 +-- .../detail/container_aware/mel_sort.h | 13 +-- .../detail/container_aware/merge_sort.h | 13 +-- .../detail/container_aware/selection_sort.h | 15 +-- include/cpp-sort/detail/empty_sorter.h | 3 +- .../cpp-sort/detail/low_comparisons/sort10.h | 14 +-- .../cpp-sort/detail/low_comparisons/sort11.h | 14 +-- .../cpp-sort/detail/low_comparisons/sort12.h | 16 +--- .../cpp-sort/detail/low_comparisons/sort13.h | 16 +--- .../cpp-sort/detail/low_comparisons/sort2.h | 14 +-- .../cpp-sort/detail/low_comparisons/sort3.h | 10 +- .../cpp-sort/detail/low_comparisons/sort4.h | 14 +-- .../cpp-sort/detail/low_comparisons/sort5.h | 12 +-- .../cpp-sort/detail/low_comparisons/sort6.h | 12 +-- .../cpp-sort/detail/low_comparisons/sort7.h | 13 +-- .../cpp-sort/detail/low_comparisons/sort8.h | 14 +-- .../cpp-sort/detail/low_comparisons/sort9.h | 12 +-- include/cpp-sort/detail/low_moves/sort2.h | 5 +- include/cpp-sort/detail/low_moves/sort3.h | 5 +- include/cpp-sort/detail/low_moves/sort4.h | 5 +- include/cpp-sort/detail/move.h | 10 +- .../cpp-sort/detail/schwartz_small_array.h | 6 +- include/cpp-sort/detail/ska_sort.h | 6 +- .../cpp-sort/detail/sorting_network/sort10.h | 2 +- .../cpp-sort/detail/sorting_network/sort11.h | 2 +- .../cpp-sort/detail/sorting_network/sort12.h | 2 +- .../cpp-sort/detail/sorting_network/sort13.h | 2 +- .../cpp-sort/detail/sorting_network/sort14.h | 2 +- .../cpp-sort/detail/sorting_network/sort15.h | 2 +- .../cpp-sort/detail/sorting_network/sort16.h | 2 +- .../cpp-sort/detail/sorting_network/sort17.h | 2 +- .../cpp-sort/detail/sorting_network/sort18.h | 2 +- .../cpp-sort/detail/sorting_network/sort19.h | 2 +- .../cpp-sort/detail/sorting_network/sort2.h | 2 +- .../cpp-sort/detail/sorting_network/sort20.h | 2 +- .../cpp-sort/detail/sorting_network/sort21.h | 2 +- .../cpp-sort/detail/sorting_network/sort22.h | 2 +- .../cpp-sort/detail/sorting_network/sort23.h | 2 +- .../cpp-sort/detail/sorting_network/sort24.h | 2 +- .../cpp-sort/detail/sorting_network/sort25.h | 2 +- .../cpp-sort/detail/sorting_network/sort26.h | 2 +- .../cpp-sort/detail/sorting_network/sort27.h | 2 +- .../cpp-sort/detail/sorting_network/sort28.h | 2 +- .../cpp-sort/detail/sorting_network/sort29.h | 2 +- .../cpp-sort/detail/sorting_network/sort3.h | 2 +- .../cpp-sort/detail/sorting_network/sort30.h | 2 +- .../cpp-sort/detail/sorting_network/sort31.h | 2 +- .../cpp-sort/detail/sorting_network/sort32.h | 2 +- .../cpp-sort/detail/sorting_network/sort4.h | 2 +- .../cpp-sort/detail/sorting_network/sort5.h | 2 +- .../cpp-sort/detail/sorting_network/sort6.h | 2 +- .../cpp-sort/detail/sorting_network/sort7.h | 2 +- .../cpp-sort/detail/sorting_network/sort8.h | 2 +- .../cpp-sort/detail/sorting_network/sort9.h | 2 +- .../detail/spreadsort/detail/common.h | 5 +- .../detail/spreadsort/detail/float_sort.h | 7 +- .../detail/spreadsort/detail/integer_sort.h | 7 +- .../detail/spreadsort/detail/string_sort.h | 7 +- .../detail/stable_adapter_self_sort_adapter.h | 11 ++- include/cpp-sort/detail/swap_if.h | 36 +++---- include/cpp-sort/detail/type_traits.h | 18 +++- .../cpp-sort/fixed/low_comparisons_sorter.h | 10 ++ include/cpp-sort/fixed/low_moves_sorter.h | 2 +- .../fixed/merge_exchange_network_sorter.h | 3 +- .../fixed/odd_even_merge_network_sorter.h | 3 +- .../cpp-sort/fixed/sorting_network_sorter.h | 1 + include/cpp-sort/probes/block.h | 5 +- include/cpp-sort/probes/dis.h | 4 +- include/cpp-sort/probes/enc.h | 3 +- include/cpp-sort/probes/exc.h | 5 +- include/cpp-sort/probes/ham.h | 5 +- include/cpp-sort/probes/inv.h | 5 +- include/cpp-sort/probes/max.h | 5 +- include/cpp-sort/probes/mono.h | 3 +- include/cpp-sort/probes/osc.h | 5 +- include/cpp-sort/probes/rem.h | 5 +- include/cpp-sort/probes/runs.h | 3 +- include/cpp-sort/probes/sus.h | 3 +- include/cpp-sort/refined.h | 6 +- include/cpp-sort/sort.h | 19 ++-- include/cpp-sort/sorter_facade.h | 96 +++++++++---------- include/cpp-sort/sorters/block_sorter.h | 3 +- .../cpp-sort/sorters/cartesian_tree_sorter.h | 5 +- include/cpp-sort/sorters/counting_sorter.h | 8 +- include/cpp-sort/sorters/drop_merge_sorter.h | 5 +- include/cpp-sort/sorters/grail_sorter.h | 5 +- include/cpp-sort/sorters/heap_sorter.h | 5 +- include/cpp-sort/sorters/insertion_sorter.h | 5 +- include/cpp-sort/sorters/mel_sorter.h | 5 +- .../cpp-sort/sorters/merge_insertion_sorter.h | 5 +- include/cpp-sort/sorters/merge_sorter.h | 7 +- include/cpp-sort/sorters/pdq_sorter.h | 5 +- include/cpp-sort/sorters/poplar_sorter.h | 5 +- include/cpp-sort/sorters/quick_merge_sorter.h | 7 +- include/cpp-sort/sorters/quick_sorter.h | 7 +- include/cpp-sort/sorters/selection_sorter.h | 5 +- include/cpp-sort/sorters/ska_sorter.h | 6 +- include/cpp-sort/sorters/slab_sorter.h | 3 +- include/cpp-sort/sorters/smooth_sorter.h | 5 +- include/cpp-sort/sorters/spin_sorter.h | 5 +- include/cpp-sort/sorters/split_sorter.h | 5 +- .../spread_sorter/float_spread_sorter.h | 5 +- .../spread_sorter/integer_spread_sorter.h | 5 +- .../spread_sorter/string_spread_sorter.h | 15 +-- include/cpp-sort/sorters/std_sorter.h | 7 +- include/cpp-sort/sorters/tim_sorter.h | 5 +- include/cpp-sort/sorters/verge_sorter.h | 7 +- include/cpp-sort/sorters/wiki_sorter.h | 3 +- include/cpp-sort/stable_sort.h | 18 ++-- include/cpp-sort/utility/as_function.h | 6 +- include/cpp-sort/utility/functional.h | 16 ++-- include/cpp-sort/utility/iter_move.h | 4 +- include/cpp-sort/utility/size.h | 6 +- 130 files changed, 455 insertions(+), 481 deletions(-) diff --git a/include/cpp-sort/adapters/container_aware_adapter.h b/include/cpp-sort/adapters/container_aware_adapter.h index adac55a5..51a26116 100644 --- a/include/cpp-sort/adapters/container_aware_adapter.h +++ b/include/cpp-sort/adapters/container_aware_adapter.h @@ -103,7 +103,7 @@ namespace cppsort typename Iterable > auto operator()(Iterable& iterable) const - -> std::enable_if_t< + -> detail::enable_if_t< detail::can_sort::value, conditional_t< Stability, @@ -120,7 +120,7 @@ namespace cppsort typename Iterable > auto operator()(Iterable& iterable) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::can_sort::value, conditional_t< Stability, @@ -138,7 +138,7 @@ namespace cppsort typename Compare > auto operator()(Iterable& iterable, Compare compare) const - -> std::enable_if_t< + -> detail::enable_if_t< detail::can_comparison_sort::value, conditional_t< Stability, @@ -156,7 +156,7 @@ namespace cppsort typename Compare > auto operator()(Iterable& iterable, Compare compare) const - -> std::enable_if_t< + -> detail::enable_if_t< not is_projection::value && not detail::can_comparison_sort::value, conditional_t< @@ -175,7 +175,7 @@ namespace cppsort typename Projection > auto operator()(Iterable& iterable, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::can_comparison_sort::value && detail::can_projection_sort::value, conditional_t< @@ -194,7 +194,7 @@ namespace cppsort typename Projection > auto operator()(Iterable& iterable, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::can_projection_sort::value && detail::can_comparison_projection_sort, Projection>::value, conditional_t< @@ -215,7 +215,7 @@ namespace cppsort typename Projection > auto operator()(Iterable& iterable, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::can_projection_sort::value && not detail::can_comparison_projection_sort, Projection>::value && detail::can_comparison_sort< @@ -241,7 +241,7 @@ namespace cppsort typename Projection > auto operator()(Iterable& iterable, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< is_projection::value && not detail::can_projection_sort::value && not detail::can_comparison_projection_sort, Projection>::value && @@ -267,7 +267,7 @@ namespace cppsort typename Projection > auto operator()(Iterable& iterable, Compare compare, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< detail::can_comparison_projection_sort::value, conditional_t< Stability, @@ -288,7 +288,7 @@ namespace cppsort typename Projection > auto operator()(Iterable& iterable, Compare compare, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::can_comparison_projection_sort::value && detail::can_comparison_sort< Sorter, @@ -314,7 +314,7 @@ namespace cppsort typename Projection > auto operator()(Iterable& iterable, Compare compare, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::can_comparison_projection_sort::value && not detail::can_comparison_sort< Sorter, diff --git a/include/cpp-sort/adapters/counting_adapter.h b/include/cpp-sort/adapters/counting_adapter.h index 6107f637..47c6b286 100644 --- a/include/cpp-sort/adapters/counting_adapter.h +++ b/include/cpp-sort/adapters/counting_adapter.h @@ -17,6 +17,7 @@ #include #include "../detail/checkers.h" #include "../detail/comparison_counter.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -40,7 +41,7 @@ namespace cppsort template< typename Iterable, typename Compare = std::less<>, - typename = std::enable_if_t< + typename = detail::enable_if_t< not is_projection_v > > @@ -56,7 +57,7 @@ namespace cppsort template< typename Iterator, typename Compare = std::less<>, - typename = std::enable_if_t< + typename = detail::enable_if_t< not is_projection_iterator_v > > @@ -73,7 +74,7 @@ namespace cppsort typename Iterable, typename Compare, typename Projection, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_v > > @@ -90,7 +91,7 @@ namespace cppsort typename Iterator, typename Compare, typename Projection, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_iterator_v > > diff --git a/include/cpp-sort/adapters/hybrid_adapter.h b/include/cpp-sort/adapters/hybrid_adapter.h index aff28104..33fec453 100644 --- a/include/cpp-sort/adapters/hybrid_adapter.h +++ b/include/cpp-sort/adapters/hybrid_adapter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2019 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_ADAPTERS_HYBRID_ADAPTER_H_ @@ -115,7 +115,7 @@ namespace cppsort template static auto _detail_stability(choice, Args&&... args) - -> std::enable_if_t< + -> detail::enable_if_t< is_invocable_v, is_stable >; @@ -394,7 +394,7 @@ namespace cppsort template static constexpr auto get_flat_tuple(Sorter&& value) - -> std::enable_if_t< + -> detail::enable_if_t< not detail::is_hybrid_adapter::value, std::tuple&&> > @@ -404,7 +404,7 @@ namespace cppsort template static constexpr auto get_flat_tuple(Sorter&& value) - -> std::enable_if_t< + -> detail::enable_if_t< detail::is_hybrid_adapter::value, decltype(get_sorters_from_impl(std::move(value))) > diff --git a/include/cpp-sort/adapters/indirect_adapter.h b/include/cpp-sort/adapters/indirect_adapter.h index dd026af2..3cc03a5f 100644 --- a/include/cpp-sort/adapters/indirect_adapter.h +++ b/include/cpp-sort/adapters/indirect_adapter.h @@ -136,7 +136,7 @@ namespace cppsort typename ForwardIterable, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_v > > @@ -155,7 +155,7 @@ namespace cppsort typename ForwardIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/adapters/schwartz_adapter.h b/include/cpp-sort/adapters/schwartz_adapter.h index f157c1d1..9e146211 100644 --- a/include/cpp-sort/adapters/schwartz_adapter.h +++ b/include/cpp-sort/adapters/schwartz_adapter.h @@ -111,7 +111,7 @@ namespace cppsort typename ForwardIterable, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_v > > @@ -127,7 +127,7 @@ namespace cppsort typename ForwardIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_iterator_v > > @@ -142,7 +142,7 @@ namespace cppsort template> auto operator()(ForwardIterable&& iterable, Compare compare={}) const - -> std::enable_if_t< + -> detail::enable_if_t< not is_projection_v, decltype(this->get()(std::forward(iterable), std::move(compare))) > @@ -154,7 +154,7 @@ namespace cppsort template> auto operator()(ForwardIterator first, ForwardIterator last, Compare compare={}) const - -> std::enable_if_t< + -> detail::enable_if_t< not is_projection_iterator_v, decltype(this->get()(std::move(first), std::move(last), std::move(compare))) > diff --git a/include/cpp-sort/adapters/self_sort_adapter.h b/include/cpp-sort/adapters/self_sort_adapter.h index 0d568867..402539a4 100644 --- a/include/cpp-sort/adapters/self_sort_adapter.h +++ b/include/cpp-sort/adapters/self_sort_adapter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2019 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_ADAPTERS_SELF_SORT_ADAPTER_H_ @@ -81,7 +81,7 @@ namespace cppsort template auto operator()(Iterable&& iterable, Args&&... args) const - -> std::enable_if_t< + -> detail::enable_if_t< detail::has_sort_method, decltype(std::forward(iterable).sort(utility::as_function(args)...)) > @@ -91,7 +91,7 @@ namespace cppsort template auto operator()(Iterable&& iterable, Args&&... args) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_sort_method && detail::has_stable_sort_method, decltype(std::forward(iterable).stable_sort(utility::as_function(args)...)) @@ -102,7 +102,7 @@ namespace cppsort template auto operator()(Iterable&& iterable, Args&&... args) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_sort_method && not detail::has_stable_sort_method, decltype(this->get()(std::forward(iterable), std::forward(args)...)) diff --git a/include/cpp-sort/adapters/small_array_adapter.h b/include/cpp-sort/adapters/small_array_adapter.h index 7d0e628a..942a4ebe 100644 --- a/include/cpp-sort/adapters/small_array_adapter.h +++ b/include/cpp-sort/adapters/small_array_adapter.h @@ -71,7 +71,7 @@ namespace cppsort typename... Args > auto operator()(std::array& array, Args&&... args) const - -> std::enable_if_t< + -> detail::enable_if_t< detail::is_in_pack, decltype(FixedSizeSorter{}(array, std::forward(args)...)) > @@ -83,10 +83,10 @@ namespace cppsort typename T, std::size_t N, typename... Args, - typename = std::enable_if_t> + typename = detail::enable_if_t> > auto operator()(T (&array)[N], Args&&... args) const - -> std::enable_if_t< + -> detail::enable_if_t< detail::is_in_pack, decltype(FixedSizeSorter{}(array, std::forward(args)...)) > diff --git a/include/cpp-sort/adapters/stable_adapter.h b/include/cpp-sort/adapters/stable_adapter.h index 122c2dcd..5d329a46 100644 --- a/include/cpp-sort/adapters/stable_adapter.h +++ b/include/cpp-sort/adapters/stable_adapter.h @@ -152,7 +152,7 @@ namespace cppsort typename ForwardIterable, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_v > > @@ -169,7 +169,7 @@ namespace cppsort typename ForwardIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_iterator_v > > @@ -225,7 +225,7 @@ namespace cppsort template< typename... Args, - typename = std::enable_if_t> + typename = detail::enable_if_t> > auto operator()(Args&&... args) const -> decltype(this->get()(std::forward(args)...)) @@ -235,7 +235,7 @@ namespace cppsort template< typename... Args, - typename = std::enable_if_t>, + typename = detail::enable_if_t>, typename = void > auto operator()(Args&&... args) const diff --git a/include/cpp-sort/adapters/verge_adapter.h b/include/cpp-sort/adapters/verge_adapter.h index 3fe6871d..7c9cc4b1 100644 --- a/include/cpp-sort/adapters/verge_adapter.h +++ b/include/cpp-sort/adapters/verge_adapter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2020 Morwenn + * Copyright (c) 2017-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_ADAPTERS_VERGE_ADAPTER_H_ @@ -18,6 +18,7 @@ #include #include #include "../detail/vergesort.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -40,7 +41,7 @@ namespace cppsort typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_iterator_v > > diff --git a/include/cpp-sort/comparators/case_insensitive_less.h b/include/cpp-sort/comparators/case_insensitive_less.h index 69f84649..ff9d6add 100644 --- a/include/cpp-sort/comparators/case_insensitive_less.h +++ b/include/cpp-sort/comparators/case_insensitive_less.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2019 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_COMPARATORS_CASE_INSENSITIVE_LESS_H_ @@ -102,7 +102,7 @@ namespace cppsort template< typename T, - typename = std::enable_if_t> + typename = detail::enable_if_t> > auto refine() const -> adl_barrier::refined_case_insensitive_less_locale_fn @@ -125,7 +125,7 @@ namespace cppsort template< typename T, - typename = std::enable_if_t> + typename = detail::enable_if_t> > auto refine() const -> adl_barrier::refined_case_insensitive_less_fn @@ -184,7 +184,7 @@ namespace cppsort template auto operator()(const T& lhs, const T& rhs) const - -> std::enable_if_t< + -> detail::enable_if_t< not is_invocable_r_v, decltype(case_insensitive_less(lhs, rhs, loc)) > @@ -194,7 +194,7 @@ namespace cppsort template auto operator()(const T& lhs, const T& rhs) const - -> std::enable_if_t< + -> detail::enable_if_t< is_invocable_r_v, bool > @@ -224,7 +224,7 @@ namespace cppsort template auto operator()(const T& lhs, const T& rhs) const - -> std::enable_if_t< + -> detail::enable_if_t< negation>::value, decltype(case_insensitive_less(lhs, rhs)) > @@ -234,7 +234,7 @@ namespace cppsort template auto operator()(const T& lhs, const T& rhs) const - -> std::enable_if_t< + -> detail::enable_if_t< conjunction< is_invocable_r, negation> @@ -247,7 +247,7 @@ namespace cppsort template auto operator()(const T& lhs, const T& rhs) const - -> std::enable_if_t< + -> detail::enable_if_t< conjunction< is_invocable_r, is_invocable_r diff --git a/include/cpp-sort/comparators/partial_greater.h b/include/cpp-sort/comparators/partial_greater.h index 2d50750c..fe5c2bf9 100644 --- a/include/cpp-sort/comparators/partial_greater.h +++ b/include/cpp-sort/comparators/partial_greater.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2019 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_COMPARATORS_PARTIAL_GREATER_H_ @@ -13,6 +13,7 @@ #include #include #include +#include "../detail/type_traits.h" namespace cppsort { @@ -23,7 +24,7 @@ namespace cppsort template auto partial_greater(T lhs, T rhs) noexcept - -> std::enable_if_t::value, bool> + -> detail::enable_if_t::value, bool> { return lhs > rhs; } diff --git a/include/cpp-sort/comparators/partial_less.h b/include/cpp-sort/comparators/partial_less.h index 4fece372..7bebbaf9 100644 --- a/include/cpp-sort/comparators/partial_less.h +++ b/include/cpp-sort/comparators/partial_less.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2019 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_COMPARATORS_PARTIAL_LESS_H_ @@ -13,6 +13,7 @@ #include #include #include +#include "../detail/type_traits.h" namespace cppsort { @@ -23,7 +24,7 @@ namespace cppsort template auto partial_less(T lhs, T rhs) noexcept - -> std::enable_if_t::value, bool> + -> detail::enable_if_t::value, bool> { return lhs < rhs; } diff --git a/include/cpp-sort/comparators/total_greater.h b/include/cpp-sort/comparators/total_greater.h index 77aa1ca3..80c6bc72 100644 --- a/include/cpp-sort/comparators/total_greater.h +++ b/include/cpp-sort/comparators/total_greater.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2019 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_COMPARATORS_TOTAL_GREATER_H_ @@ -14,6 +14,7 @@ #include #include #include "../detail/floating_point_weight.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -24,7 +25,7 @@ namespace cppsort template constexpr auto total_greater(T lhs, T rhs) noexcept - -> std::enable_if_t::value, bool> + -> detail::enable_if_t::value, bool> { return lhs > rhs; } @@ -34,7 +35,7 @@ namespace cppsort template auto total_greater(T lhs, T rhs) - -> std::enable_if_t::value, bool> + -> detail::enable_if_t::value, bool> { if (std::isfinite(lhs) && std::isfinite(rhs)) { if (lhs == 0 && rhs == 0) { diff --git a/include/cpp-sort/comparators/total_less.h b/include/cpp-sort/comparators/total_less.h index b4d23c82..6e09ae82 100644 --- a/include/cpp-sort/comparators/total_less.h +++ b/include/cpp-sort/comparators/total_less.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2019 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_COMPARATORS_TOTAL_LESS_H_ @@ -14,6 +14,7 @@ #include #include #include "../detail/floating_point_weight.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -24,7 +25,7 @@ namespace cppsort template constexpr auto total_less(T lhs, T rhs) noexcept - -> std::enable_if_t::value, bool> + -> detail::enable_if_t::value, bool> { return lhs < rhs; } @@ -34,7 +35,7 @@ namespace cppsort template auto total_less(T lhs, T rhs) - -> std::enable_if_t::value, bool> + -> detail::enable_if_t::value, bool> { if (std::isfinite(lhs) && std::isfinite(rhs)) { if (lhs == 0 && rhs == 0) { diff --git a/include/cpp-sort/comparators/weak_greater.h b/include/cpp-sort/comparators/weak_greater.h index a7f5dba7..00ed6930 100644 --- a/include/cpp-sort/comparators/weak_greater.h +++ b/include/cpp-sort/comparators/weak_greater.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2019 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_COMPARATORS_WEAK_GREATER_H_ @@ -15,6 +15,7 @@ #include #include #include "../detail/floating_point_weight.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -25,7 +26,7 @@ namespace cppsort template auto weak_greater(T lhs, T rhs) - -> std::enable_if_t::value, bool> + -> detail::enable_if_t::value, bool> { if (std::isfinite(lhs) && std::isfinite(rhs)) { return lhs > rhs; diff --git a/include/cpp-sort/comparators/weak_less.h b/include/cpp-sort/comparators/weak_less.h index 060a7273..f4ecda3f 100644 --- a/include/cpp-sort/comparators/weak_less.h +++ b/include/cpp-sort/comparators/weak_less.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2019 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_COMPARATORS_WEAK_LESS_H_ @@ -15,6 +15,7 @@ #include #include #include "../detail/floating_point_weight.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -25,7 +26,7 @@ namespace cppsort template auto weak_less(T lhs, T rhs) - -> std::enable_if_t::value, bool> + -> detail::enable_if_t::value, bool> { if (std::isfinite(lhs) && std::isfinite(rhs)) { return lhs < rhs; diff --git a/include/cpp-sort/detail/bitops.h b/include/cpp-sort/detail/bitops.h index 56eebf97..56e61cd2 100644 --- a/include/cpp-sort/detail/bitops.h +++ b/include/cpp-sort/detail/bitops.h @@ -11,6 +11,7 @@ #include #include #include +#include "../detail/type_traits.h" namespace cppsort { @@ -79,14 +80,14 @@ namespace detail template constexpr auto half(Integer value) - -> std::enable_if_t::value, Integer> + -> detail::enable_if_t::value, Integer> { return static_cast(static_cast>(value) / 2); } template constexpr auto half(T value) - -> std::enable_if_t::value, T> + -> detail::enable_if_t::value, T> { return value / 2; } diff --git a/include/cpp-sort/detail/container_aware/insertion_sort.h b/include/cpp-sort/detail/container_aware/insertion_sort.h index c3fe6634..03bd1c39 100644 --- a/include/cpp-sort/detail/container_aware/insertion_sort.h +++ b/include/cpp-sort/detail/container_aware/insertion_sort.h @@ -122,7 +122,7 @@ namespace cppsort template auto operator()(std::list& iterable, Compare compare) const - -> std::enable_if_t< + -> detail::enable_if_t< is_projection_v, Compare> > { @@ -131,7 +131,7 @@ namespace cppsort template auto operator()(std::list& iterable, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< is_projection_v> > { @@ -142,7 +142,7 @@ namespace cppsort typename Compare, typename Projection, typename... Args, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_v, Compare> > > @@ -165,7 +165,7 @@ namespace cppsort template auto operator()(std::forward_list& iterable, Compare compare) const - -> std::enable_if_t< + -> detail::enable_if_t< is_projection_v, Compare> > { @@ -174,7 +174,7 @@ namespace cppsort template auto operator()(std::forward_list& iterable, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< is_projection_v> > { @@ -185,7 +185,7 @@ namespace cppsort typename Compare, typename Projection, typename... Args, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_v, Compare> > > @@ -201,7 +201,7 @@ namespace cppsort template< typename First, typename... Args, - typename = std::enable_if_t< + typename = detail::enable_if_t< not detail::is_std_list>::value && not detail::is_std_forward_list>::value > diff --git a/include/cpp-sort/detail/container_aware/mel_sort.h b/include/cpp-sort/detail/container_aware/mel_sort.h index 71b02cb9..f04b19d7 100644 --- a/include/cpp-sort/detail/container_aware/mel_sort.h +++ b/include/cpp-sort/detail/container_aware/mel_sort.h @@ -23,6 +23,7 @@ #include #include "../functional.h" #include "../lower_bound.h" +#include "../type_traits.h" namespace cppsort { @@ -216,7 +217,7 @@ namespace cppsort template auto operator()(std::list& iterable, Compare compare) const - -> std::enable_if_t< + -> detail::enable_if_t< is_projection_v, Compare> > { @@ -225,7 +226,7 @@ namespace cppsort template auto operator()(std::list& iterable, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< is_projection_v> > { @@ -236,7 +237,7 @@ namespace cppsort typename Compare, typename Projection, typename... Args, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_v, Compare> > > @@ -259,7 +260,7 @@ namespace cppsort template auto operator()(std::forward_list& iterable, Compare compare) const - -> std::enable_if_t< + -> detail::enable_if_t< is_projection_v, Compare> > { @@ -268,7 +269,7 @@ namespace cppsort template auto operator()(std::forward_list& iterable, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< is_projection_v> > { @@ -279,7 +280,7 @@ namespace cppsort typename Compare, typename Projection, typename... Args, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_v, Compare> > > diff --git a/include/cpp-sort/detail/container_aware/merge_sort.h b/include/cpp-sort/detail/container_aware/merge_sort.h index 94836f8d..5bf3c750 100644 --- a/include/cpp-sort/detail/container_aware/merge_sort.h +++ b/include/cpp-sort/detail/container_aware/merge_sort.h @@ -20,6 +20,7 @@ #include #include #include +#include "../type_traits.h" namespace cppsort { @@ -92,7 +93,7 @@ namespace cppsort template auto operator()(std::list& iterable, Compare compare) const - -> std::enable_if_t< + -> detail::enable_if_t< is_projection_v, Compare> > { @@ -101,7 +102,7 @@ namespace cppsort template auto operator()(std::list& iterable, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< is_projection_v> > { @@ -112,7 +113,7 @@ namespace cppsort typename Compare, typename Projection, typename... Args, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_v, Compare> > > @@ -136,7 +137,7 @@ namespace cppsort template auto operator()(std::forward_list& iterable, Compare compare) const - -> std::enable_if_t< + -> detail::enable_if_t< is_projection_v, Compare> > { @@ -146,7 +147,7 @@ namespace cppsort template auto operator()(std::forward_list& iterable, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< is_projection_v> > { @@ -158,7 +159,7 @@ namespace cppsort typename Compare, typename Projection, typename... Args, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_v, Compare> > > diff --git a/include/cpp-sort/detail/container_aware/selection_sort.h b/include/cpp-sort/detail/container_aware/selection_sort.h index 7d9b1be8..6bc6972d 100644 --- a/include/cpp-sort/detail/container_aware/selection_sort.h +++ b/include/cpp-sort/detail/container_aware/selection_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_SELECTION_SORT_H_ @@ -20,6 +20,7 @@ #include #include #include "../min_element.h" +#include "../type_traits.h" namespace cppsort { @@ -100,7 +101,7 @@ namespace cppsort template auto operator()(std::list& iterable, Compare compare) const - -> std::enable_if_t< + -> detail::enable_if_t< is_projection_v, Compare> > { @@ -109,7 +110,7 @@ namespace cppsort template auto operator()(std::list& iterable, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< is_projection_v> > { @@ -120,7 +121,7 @@ namespace cppsort typename Compare, typename Projection, typename... Args, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_v, Compare> > > @@ -143,7 +144,7 @@ namespace cppsort template auto operator()(std::forward_list& iterable, Compare compare) const - -> std::enable_if_t< + -> detail::enable_if_t< is_projection_v, Compare> > { @@ -152,7 +153,7 @@ namespace cppsort template auto operator()(std::forward_list& iterable, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< is_projection_v> > { @@ -163,7 +164,7 @@ namespace cppsort typename Compare, typename Projection, typename... Args, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_v, Compare> > > diff --git a/include/cpp-sort/detail/empty_sorter.h b/include/cpp-sort/detail/empty_sorter.h index e9523979..99be395a 100644 --- a/include/cpp-sort/detail/empty_sorter.h +++ b/include/cpp-sort/detail/empty_sorter.h @@ -16,6 +16,7 @@ #include #include #include "attributes.h" +#include "type_traits.h" namespace cppsort { @@ -32,7 +33,7 @@ namespace detail typename ForwardIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/low_comparisons/sort10.h b/include/cpp-sort/detail/low_comparisons/sort10.h index d3527d16..e6b984c8 100644 --- a/include/cpp-sort/detail/low_comparisons/sort10.h +++ b/include/cpp-sort/detail/low_comparisons/sort10.h @@ -1,20 +1,10 @@ /* - * Copyright (c) 2015-2016 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_LOW_COMPARISONS_SORT10_H_ #define CPPSORT_DETAIL_LOW_COMPARISONS_SORT10_H_ -//////////////////////////////////////////////////////////// -// Headers -//////////////////////////////////////////////////////////// -#include -#include -#include -#include -#include -#include "../front_insert.h" - namespace cppsort { namespace detail @@ -26,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/low_comparisons/sort11.h b/include/cpp-sort/detail/low_comparisons/sort11.h index 98cefa7f..313d2361 100644 --- a/include/cpp-sort/detail/low_comparisons/sort11.h +++ b/include/cpp-sort/detail/low_comparisons/sort11.h @@ -1,20 +1,10 @@ /* - * Copyright (c) 2015-2016 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_LOW_COMPARISONS_SORT11_H_ #define CPPSORT_DETAIL_LOW_COMPARISONS_SORT11_H_ -//////////////////////////////////////////////////////////// -// Headers -//////////////////////////////////////////////////////////// -#include -#include -#include -#include -#include -#include "../front_insert.h" - namespace cppsort { namespace detail @@ -26,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/low_comparisons/sort12.h b/include/cpp-sort/detail/low_comparisons/sort12.h index a795bc01..17379454 100644 --- a/include/cpp-sort/detail/low_comparisons/sort12.h +++ b/include/cpp-sort/detail/low_comparisons/sort12.h @@ -1,22 +1,10 @@ /* - * Copyright (c) 2015-2017 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_LOW_COMPARISONS_SORT12_H_ #define CPPSORT_DETAIL_LOW_COMPARISONS_SORT12_H_ -//////////////////////////////////////////////////////////// -// Headers -//////////////////////////////////////////////////////////// -#include -#include -#include -#include -#include -#include -#include -#include "../swap_if.h" - namespace cppsort { namespace detail @@ -28,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/low_comparisons/sort13.h b/include/cpp-sort/detail/low_comparisons/sort13.h index 6b0a57a9..5b89e533 100644 --- a/include/cpp-sort/detail/low_comparisons/sort13.h +++ b/include/cpp-sort/detail/low_comparisons/sort13.h @@ -1,22 +1,10 @@ /* - * Copyright (c) 2015-2017 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_LOW_COMPARISONS_SORT13_H_ #define CPPSORT_DETAIL_LOW_COMPARISONS_SORT13_H_ -//////////////////////////////////////////////////////////// -// Headers -//////////////////////////////////////////////////////////// -#include -#include -#include -#include -#include -#include -#include -#include "../swap_if.h" - namespace cppsort { namespace detail @@ -28,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/low_comparisons/sort2.h b/include/cpp-sort/detail/low_comparisons/sort2.h index 85aa9018..9ff88f24 100644 --- a/include/cpp-sort/detail/low_comparisons/sort2.h +++ b/include/cpp-sort/detail/low_comparisons/sort2.h @@ -1,20 +1,10 @@ /* - * Copyright (c) 2015-2017 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_LOW_COMPARISONS_SORT2_H_ #define CPPSORT_DETAIL_LOW_COMPARISONS_SORT2_H_ -//////////////////////////////////////////////////////////// -// Headers -//////////////////////////////////////////////////////////// -#include -#include -#include -#include -#include -#include "../swap_if.h" - namespace cppsort { namespace detail @@ -26,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/low_comparisons/sort3.h b/include/cpp-sort/detail/low_comparisons/sort3.h index b4e6aa96..7197b62c 100644 --- a/include/cpp-sort/detail/low_comparisons/sort3.h +++ b/include/cpp-sort/detail/low_comparisons/sort3.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2017 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_LOW_COMPARISONS_SORT3_H_ @@ -8,12 +8,6 @@ //////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// -#include -#include -#include -#include -#include -#include #include "../rotate_left.h" #include "../rotate_right.h" @@ -28,7 +22,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/low_comparisons/sort4.h b/include/cpp-sort/detail/low_comparisons/sort4.h index 7c05be5d..2c0ab9cc 100644 --- a/include/cpp-sort/detail/low_comparisons/sort4.h +++ b/include/cpp-sort/detail/low_comparisons/sort4.h @@ -1,20 +1,10 @@ /* - * Copyright (c) 2015-2016 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_LOW_COMPARISONS_SORT4_H_ #define CPPSORT_DETAIL_LOW_COMPARISONS_SORT4_H_ -//////////////////////////////////////////////////////////// -// Headers -//////////////////////////////////////////////////////////// -#include -#include -#include -#include -#include -#include "../front_insert.h" - namespace cppsort { namespace detail @@ -26,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/low_comparisons/sort5.h b/include/cpp-sort/detail/low_comparisons/sort5.h index 611d22e5..0821d9ba 100644 --- a/include/cpp-sort/detail/low_comparisons/sort5.h +++ b/include/cpp-sort/detail/low_comparisons/sort5.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2017 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_LOW_COMPARISONS_SORT5_H_ @@ -8,15 +8,7 @@ //////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// -#include -#include -#include -#include -#include -#include -#include #include "../rotate_right.h" -#include "../swap_if.h" namespace cppsort { @@ -29,7 +21,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/low_comparisons/sort6.h b/include/cpp-sort/detail/low_comparisons/sort6.h index 40d5c65b..70bf7a5d 100644 --- a/include/cpp-sort/detail/low_comparisons/sort6.h +++ b/include/cpp-sort/detail/low_comparisons/sort6.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2020 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_LOW_COMPARISONS_SORT6_H_ @@ -8,15 +8,7 @@ //////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// -#include -#include -#include -#include -#include -#include -#include #include "../rotate_right.h" -#include "../swap_if.h" namespace cppsort { @@ -29,7 +21,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/low_comparisons/sort7.h b/include/cpp-sort/detail/low_comparisons/sort7.h index ef2ef83d..30638590 100644 --- a/include/cpp-sort/detail/low_comparisons/sort7.h +++ b/include/cpp-sort/detail/low_comparisons/sort7.h @@ -1,19 +1,10 @@ /* - * Copyright (c) 2015-2016 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_LOW_COMPARISONS_SORT7_H_ #define CPPSORT_DETAIL_LOW_COMPARISONS_SORT7_H_ -//////////////////////////////////////////////////////////// -// Headers -//////////////////////////////////////////////////////////// -#include -#include -#include -#include -#include -#include "../front_insert.h" namespace cppsort { @@ -26,7 +17,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/low_comparisons/sort8.h b/include/cpp-sort/detail/low_comparisons/sort8.h index b80d9ede..c760c3f8 100644 --- a/include/cpp-sort/detail/low_comparisons/sort8.h +++ b/include/cpp-sort/detail/low_comparisons/sort8.h @@ -1,20 +1,10 @@ /* - * Copyright (c) 2015-2016 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_LOW_COMPARISONS_SORT8_H_ #define CPPSORT_DETAIL_LOW_COMPARISONS_SORT8_H_ -//////////////////////////////////////////////////////////// -// Headers -//////////////////////////////////////////////////////////// -#include -#include -#include -#include -#include -#include "../front_insert.h" - namespace cppsort { namespace detail @@ -26,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/low_comparisons/sort9.h b/include/cpp-sort/detail/low_comparisons/sort9.h index abd48ff5..d0475d17 100644 --- a/include/cpp-sort/detail/low_comparisons/sort9.h +++ b/include/cpp-sort/detail/low_comparisons/sort9.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2017 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_LOW_COMPARISONS_SORT9_H_ @@ -8,15 +8,7 @@ //////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// -#include -#include -#include -#include -#include -#include -#include #include "../rotate_right.h" -#include "../swap_if.h" namespace cppsort { @@ -29,7 +21,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/low_moves/sort2.h b/include/cpp-sort/detail/low_moves/sort2.h index 0d088fb6..daf5c256 100644 --- a/include/cpp-sort/detail/low_moves/sort2.h +++ b/include/cpp-sort/detail/low_moves/sort2.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2017 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_LOW_MOVES_SORT2_H_ @@ -14,6 +14,7 @@ #include #include #include "../swap_if.h" +#include "../type_traits.h" namespace cppsort { @@ -26,7 +27,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/low_moves/sort3.h b/include/cpp-sort/detail/low_moves/sort3.h index ad007d4c..e2ecf5c4 100644 --- a/include/cpp-sort/detail/low_moves/sort3.h +++ b/include/cpp-sort/detail/low_moves/sort3.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2017 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_LOW_MOVES_SORT3_H_ @@ -16,6 +16,7 @@ #include #include "../rotate_left.h" #include "../rotate_right.h" +#include "../type_traits.h" namespace cppsort { @@ -28,7 +29,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/low_moves/sort4.h b/include/cpp-sort/detail/low_moves/sort4.h index b23234e0..e60b3153 100644 --- a/include/cpp-sort/detail/low_moves/sort4.h +++ b/include/cpp-sort/detail/low_moves/sort4.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2016 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_LOW_MOVES_SORT4_H_ @@ -15,6 +15,7 @@ #include #include #include "../min_element.h" +#include "../type_traits.h" namespace cppsort { @@ -27,7 +28,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/move.h b/include/cpp-sort/detail/move.h index 2ebbdad9..b8ba06da 100644 --- a/include/cpp-sort/detail/move.h +++ b/include/cpp-sort/detail/move.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Morwenn + * Copyright (c) 2019-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_MOVE_H_ @@ -56,7 +56,7 @@ namespace detail template auto move(InputIterator first, InputIterator last, OutputIterator result) - -> std::enable_if_t< + -> detail::enable_if_t< not is_invocable_v, OutputIterator > @@ -66,7 +66,7 @@ namespace detail template auto move(InputIterator first, InputIterator last, OutputIterator result) - -> std::enable_if_t< + -> detail::enable_if_t< is_invocable_v, OutputIterator > @@ -83,7 +83,7 @@ namespace detail template auto move_backward(InputIterator first, InputIterator last, OutputIterator result) - -> std::enable_if_t< + -> detail::enable_if_t< not is_invocable_v, OutputIterator > @@ -93,7 +93,7 @@ namespace detail template auto move_backward(InputIterator first, InputIterator last, OutputIterator result) - -> std::enable_if_t< + -> detail::enable_if_t< is_invocable_v, OutputIterator > diff --git a/include/cpp-sort/detail/schwartz_small_array.h b/include/cpp-sort/detail/schwartz_small_array.h index 2004f5f1..4465a870 100644 --- a/include/cpp-sort/detail/schwartz_small_array.h +++ b/include/cpp-sort/detail/schwartz_small_array.h @@ -38,7 +38,7 @@ namespace cppsort typename... Args > auto operator()(std::array& array, Args&&... args) const - -> std::enable_if_t< + -> detail::enable_if_t< detail::is_in_pack, decltype(schwartz_adapter>{}(array, std::forward(args)...)) > @@ -51,10 +51,10 @@ namespace cppsort typename T, std::size_t N, typename... Args, - typename = std::enable_if_t> + typename = detail::enable_if_t> > auto operator()(T (&array)[N], Args&&... args) const - -> std::enable_if_t< + -> detail::enable_if_t< detail::is_in_pack, decltype(schwartz_adapter>{}(array, std::forward(args)...)) > diff --git a/include/cpp-sort/detail/ska_sort.h b/include/cpp-sort/detail/ska_sort.h index 1233c5be..15bf2c5e 100644 --- a/include/cpp-sort/detail/ska_sort.h +++ b/include/cpp-sort/detail/ska_sort.h @@ -348,7 +348,7 @@ namespace detail }; template - struct FallbackSubKey()))>::value>>: + struct FallbackSubKey()))>::value>>: SubKey()))> {}; @@ -533,7 +533,7 @@ namespace detail }; template - struct FallbackSubKey()[0])>::value>>: + struct FallbackSubKey()[0])>::value>>: ListSubKey {}; @@ -921,7 +921,7 @@ namespace detail template struct FallbackInplaceSorter()[0])>::value>>: + detail::enable_if_t()[0])>::value>>: ListInplaceSorter {}; diff --git a/include/cpp-sort/detail/sorting_network/sort10.h b/include/cpp-sort/detail/sorting_network/sort10.h index e5ed6a93..b34e712f 100644 --- a/include/cpp-sort/detail/sorting_network/sort10.h +++ b/include/cpp-sort/detail/sorting_network/sort10.h @@ -16,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/sorting_network/sort11.h b/include/cpp-sort/detail/sorting_network/sort11.h index f5014df9..0dc582c0 100644 --- a/include/cpp-sort/detail/sorting_network/sort11.h +++ b/include/cpp-sort/detail/sorting_network/sort11.h @@ -16,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/sorting_network/sort12.h b/include/cpp-sort/detail/sorting_network/sort12.h index 2c3d0eb3..39d05c19 100644 --- a/include/cpp-sort/detail/sorting_network/sort12.h +++ b/include/cpp-sort/detail/sorting_network/sort12.h @@ -16,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/sorting_network/sort13.h b/include/cpp-sort/detail/sorting_network/sort13.h index 320aa83d..444959a0 100644 --- a/include/cpp-sort/detail/sorting_network/sort13.h +++ b/include/cpp-sort/detail/sorting_network/sort13.h @@ -16,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/sorting_network/sort14.h b/include/cpp-sort/detail/sorting_network/sort14.h index 5dccda23..11ca07c0 100644 --- a/include/cpp-sort/detail/sorting_network/sort14.h +++ b/include/cpp-sort/detail/sorting_network/sort14.h @@ -16,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/sorting_network/sort15.h b/include/cpp-sort/detail/sorting_network/sort15.h index e8c84379..539dadd7 100644 --- a/include/cpp-sort/detail/sorting_network/sort15.h +++ b/include/cpp-sort/detail/sorting_network/sort15.h @@ -16,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/sorting_network/sort16.h b/include/cpp-sort/detail/sorting_network/sort16.h index 7b44df5e..cf1be55c 100644 --- a/include/cpp-sort/detail/sorting_network/sort16.h +++ b/include/cpp-sort/detail/sorting_network/sort16.h @@ -16,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/sorting_network/sort17.h b/include/cpp-sort/detail/sorting_network/sort17.h index 77bb45b6..ae1e35c5 100644 --- a/include/cpp-sort/detail/sorting_network/sort17.h +++ b/include/cpp-sort/detail/sorting_network/sort17.h @@ -16,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/sorting_network/sort18.h b/include/cpp-sort/detail/sorting_network/sort18.h index f5a62023..7520709c 100644 --- a/include/cpp-sort/detail/sorting_network/sort18.h +++ b/include/cpp-sort/detail/sorting_network/sort18.h @@ -16,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/sorting_network/sort19.h b/include/cpp-sort/detail/sorting_network/sort19.h index c98812d2..b606b109 100644 --- a/include/cpp-sort/detail/sorting_network/sort19.h +++ b/include/cpp-sort/detail/sorting_network/sort19.h @@ -16,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/sorting_network/sort2.h b/include/cpp-sort/detail/sorting_network/sort2.h index acbf77e4..621d01a2 100644 --- a/include/cpp-sort/detail/sorting_network/sort2.h +++ b/include/cpp-sort/detail/sorting_network/sort2.h @@ -16,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/sorting_network/sort20.h b/include/cpp-sort/detail/sorting_network/sort20.h index ad03df6d..4d6b1082 100644 --- a/include/cpp-sort/detail/sorting_network/sort20.h +++ b/include/cpp-sort/detail/sorting_network/sort20.h @@ -16,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/sorting_network/sort21.h b/include/cpp-sort/detail/sorting_network/sort21.h index 33f53c37..ad36e368 100644 --- a/include/cpp-sort/detail/sorting_network/sort21.h +++ b/include/cpp-sort/detail/sorting_network/sort21.h @@ -16,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/sorting_network/sort22.h b/include/cpp-sort/detail/sorting_network/sort22.h index 8e46fd8b..95ac0388 100644 --- a/include/cpp-sort/detail/sorting_network/sort22.h +++ b/include/cpp-sort/detail/sorting_network/sort22.h @@ -16,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/sorting_network/sort23.h b/include/cpp-sort/detail/sorting_network/sort23.h index 8f69d845..4eeec33b 100644 --- a/include/cpp-sort/detail/sorting_network/sort23.h +++ b/include/cpp-sort/detail/sorting_network/sort23.h @@ -16,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/sorting_network/sort24.h b/include/cpp-sort/detail/sorting_network/sort24.h index 71d14d5e..24977333 100644 --- a/include/cpp-sort/detail/sorting_network/sort24.h +++ b/include/cpp-sort/detail/sorting_network/sort24.h @@ -16,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/sorting_network/sort25.h b/include/cpp-sort/detail/sorting_network/sort25.h index f3ee3085..53fc6fe5 100644 --- a/include/cpp-sort/detail/sorting_network/sort25.h +++ b/include/cpp-sort/detail/sorting_network/sort25.h @@ -16,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/sorting_network/sort26.h b/include/cpp-sort/detail/sorting_network/sort26.h index 47db075b..20f93ae8 100644 --- a/include/cpp-sort/detail/sorting_network/sort26.h +++ b/include/cpp-sort/detail/sorting_network/sort26.h @@ -16,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/sorting_network/sort27.h b/include/cpp-sort/detail/sorting_network/sort27.h index ce0896b3..6896fd63 100644 --- a/include/cpp-sort/detail/sorting_network/sort27.h +++ b/include/cpp-sort/detail/sorting_network/sort27.h @@ -16,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/sorting_network/sort28.h b/include/cpp-sort/detail/sorting_network/sort28.h index a62d4faa..1654aac5 100644 --- a/include/cpp-sort/detail/sorting_network/sort28.h +++ b/include/cpp-sort/detail/sorting_network/sort28.h @@ -16,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/sorting_network/sort29.h b/include/cpp-sort/detail/sorting_network/sort29.h index 3efa401d..785f8768 100644 --- a/include/cpp-sort/detail/sorting_network/sort29.h +++ b/include/cpp-sort/detail/sorting_network/sort29.h @@ -16,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/sorting_network/sort3.h b/include/cpp-sort/detail/sorting_network/sort3.h index 26225c6b..085db9a7 100644 --- a/include/cpp-sort/detail/sorting_network/sort3.h +++ b/include/cpp-sort/detail/sorting_network/sort3.h @@ -16,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/sorting_network/sort30.h b/include/cpp-sort/detail/sorting_network/sort30.h index fccb778a..4cf2257a 100644 --- a/include/cpp-sort/detail/sorting_network/sort30.h +++ b/include/cpp-sort/detail/sorting_network/sort30.h @@ -16,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/sorting_network/sort31.h b/include/cpp-sort/detail/sorting_network/sort31.h index 1f09bd60..6d089737 100644 --- a/include/cpp-sort/detail/sorting_network/sort31.h +++ b/include/cpp-sort/detail/sorting_network/sort31.h @@ -16,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/sorting_network/sort32.h b/include/cpp-sort/detail/sorting_network/sort32.h index 1ba756cf..4bb59619 100644 --- a/include/cpp-sort/detail/sorting_network/sort32.h +++ b/include/cpp-sort/detail/sorting_network/sort32.h @@ -16,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/sorting_network/sort4.h b/include/cpp-sort/detail/sorting_network/sort4.h index 152fee95..b91bc162 100644 --- a/include/cpp-sort/detail/sorting_network/sort4.h +++ b/include/cpp-sort/detail/sorting_network/sort4.h @@ -16,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/sorting_network/sort5.h b/include/cpp-sort/detail/sorting_network/sort5.h index 023c824a..10084520 100644 --- a/include/cpp-sort/detail/sorting_network/sort5.h +++ b/include/cpp-sort/detail/sorting_network/sort5.h @@ -16,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/sorting_network/sort6.h b/include/cpp-sort/detail/sorting_network/sort6.h index 383fbc6d..03787f02 100644 --- a/include/cpp-sort/detail/sorting_network/sort6.h +++ b/include/cpp-sort/detail/sorting_network/sort6.h @@ -16,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/sorting_network/sort7.h b/include/cpp-sort/detail/sorting_network/sort7.h index 8ee0367e..8fbdbbb2 100644 --- a/include/cpp-sort/detail/sorting_network/sort7.h +++ b/include/cpp-sort/detail/sorting_network/sort7.h @@ -16,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/sorting_network/sort8.h b/include/cpp-sort/detail/sorting_network/sort8.h index 16f6316c..f6c87924 100644 --- a/include/cpp-sort/detail/sorting_network/sort8.h +++ b/include/cpp-sort/detail/sorting_network/sort8.h @@ -16,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/sorting_network/sort9.h b/include/cpp-sort/detail/sorting_network/sort9.h index 34ec1a2b..cb9440d5 100644 --- a/include/cpp-sort/detail/sorting_network/sort9.h +++ b/include/cpp-sort/detail/sorting_network/sort9.h @@ -16,7 +16,7 @@ namespace detail typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/detail/spreadsort/detail/common.h b/include/cpp-sort/detail/spreadsort/detail/common.h index cc746644..58a4ab1e 100644 --- a/include/cpp-sort/detail/spreadsort/detail/common.h +++ b/include/cpp-sort/detail/spreadsort/detail/common.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2018 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ @@ -27,6 +27,7 @@ Phil Endecott and Frank Gennari #include #include #include "constants.h" +#include "../../type_traits.h" namespace cppsort { @@ -38,7 +39,7 @@ namespace detail { //Well, we're not using Boost in the end template - using disable_if_t = std::enable_if_t; + using disable_if_t = cppsort::detail::enable_if_t; //This only works on unsigned data types template diff --git a/include/cpp-sort/detail/spreadsort/detail/float_sort.h b/include/cpp-sort/detail/spreadsort/detail/float_sort.h index aff0eeb8..2264910f 100644 --- a/include/cpp-sort/detail/spreadsort/detail/float_sort.h +++ b/include/cpp-sort/detail/spreadsort/detail/float_sort.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2017 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ @@ -37,6 +37,7 @@ Phil Endecott and Frank Gennari #include "../../iterator_traits.h" #include "../../memcpy_cast.h" #include "../../pdqsort.h" +#include "../../type_traits.h" namespace cppsort { @@ -363,7 +364,7 @@ namespace detail //Checking whether the value type is a float, and trying a 32-bit integer template auto float_sort(RandomAccessIter first, RandomAccessIter last, Projection projection) - -> std::enable_if_t< + -> cppsort::detail::enable_if_t< sizeof(std::uint32_t) == sizeof(projected_t) && std::numeric_limits>::is_iec559, void @@ -378,7 +379,7 @@ namespace detail //Checking whether the value type is a double, and using a 64-bit integer template auto float_sort(RandomAccessIter first, RandomAccessIter last, Projection projection) - -> std::enable_if_t< + -> cppsort::detail::enable_if_t< sizeof(std::uint64_t) == sizeof(projected_t) && std::numeric_limits>::is_iec559, void diff --git a/include/cpp-sort/detail/spreadsort/detail/integer_sort.h b/include/cpp-sort/detail/spreadsort/detail/integer_sort.h index 50faef2c..d304f9a4 100644 --- a/include/cpp-sort/detail/spreadsort/detail/integer_sort.h +++ b/include/cpp-sort/detail/spreadsort/detail/integer_sort.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2017 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ @@ -33,6 +33,7 @@ Phil Endecott and Frank Gennari #include "common.h" #include "constants.h" #include "../../pdqsort.h" +#include "../../type_traits.h" namespace cppsort { @@ -202,7 +203,7 @@ namespace spreadsort template auto integer_sort(RandomAccessIter first, RandomAccessIter last, Div_type, Projection projection) - -> std::enable_if_t< + -> cppsort::detail::enable_if_t< sizeof(Div_type) <= sizeof(std::size_t), void > @@ -218,7 +219,7 @@ namespace spreadsort template auto integer_sort(RandomAccessIter first, RandomAccessIter last, Div_type, Projection projection) - -> std::enable_if_t< + -> cppsort::detail::enable_if_t< (sizeof(Div_type) > sizeof(std::size_t)) && sizeof(Div_type) <= sizeof(std::uintmax_t), void diff --git a/include/cpp-sort/detail/spreadsort/detail/string_sort.h b/include/cpp-sort/detail/spreadsort/detail/string_sort.h index cf749515..06cd0029 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-2020 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ @@ -33,6 +33,7 @@ Phil Endecott and Frank Gennari #include #include "common.h" #include "constants.h" +#include "../../type_traits.h" namespace cppsort { @@ -380,7 +381,7 @@ namespace spreadsort typename Unsigned_char_type> auto string_sort(RandomAccessIter first, RandomAccessIter last, Projection projection, Unsigned_char_type) - -> std::enable_if_t + -> cppsort::detail::enable_if_t { std::size_t bin_sizes[(1 << (8 * sizeof(Unsigned_char_type))) + 1]; std::vector bin_cache; @@ -393,7 +394,7 @@ namespace spreadsort typename Unsigned_char_type> auto reverse_string_sort(RandomAccessIter first, RandomAccessIter last, Projection projection, Unsigned_char_type) - -> std::enable_if_t + -> cppsort::detail::enable_if_t { std::size_t bin_sizes[(1 << (8 * sizeof(Unsigned_char_type))) + 1]; std::vector bin_cache; 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 a0ca5c46..46e810dd 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-2020 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_STABLE_ADAPTER_SELF_SORT_ADAPTER_H_ @@ -17,6 +17,7 @@ #include #include #include "checkers.h" +#include "type_traits.h" namespace cppsort { @@ -45,7 +46,7 @@ namespace cppsort template auto operator()(Iterable&& iterable, Args&&... args) const - -> std::enable_if_t< + -> detail::enable_if_t< detail::has_stable_sort_method, decltype(std::forward(iterable).stable_sort(utility::as_function(args)...)) > @@ -55,7 +56,7 @@ namespace cppsort template auto operator()(Iterable&& iterable, Args&&... args) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_stable_sort_method, decltype(this->get()(std::forward(iterable), std::forward(args)...)) > @@ -84,7 +85,7 @@ namespace cppsort template< typename T, typename Compare, - typename = std::enable_if_t&>> + typename = detail::enable_if_t&>> > auto operator()(std::forward_list& iterable, Compare compare) const -> void @@ -102,7 +103,7 @@ namespace cppsort template< typename T, typename Compare, - typename = std::enable_if_t&>> + typename = detail::enable_if_t&>> > auto operator()(std::list& iterable, Compare compare) const -> void diff --git a/include/cpp-sort/detail/swap_if.h b/include/cpp-sort/detail/swap_if.h index 1a1cb511..1d3a97e0 100644 --- a/include/cpp-sort/detail/swap_if.h +++ b/include/cpp-sort/detail/swap_if.h @@ -48,7 +48,7 @@ namespace detail template auto swap_if(Integer& x, Integer& y, std::less<>, utility::identity) noexcept - -> std::enable_if_t::value> + -> detail::enable_if_t::value> { Integer dx = x; x = (std::min)(x, y); @@ -57,7 +57,7 @@ namespace detail template auto swap_if(Float& x, Float& y, std::less<>, utility::identity) noexcept - -> std::enable_if_t::value> + -> detail::enable_if_t::value> { Float dx = x; x = (std::min)(x, y); @@ -66,7 +66,7 @@ namespace detail template auto swap_if(Integer& x, Integer& y, std::greater<>, utility::identity) noexcept - -> std::enable_if_t::value> + -> detail::enable_if_t::value> { Integer dx = x; x = (std::max)(x, y); @@ -75,7 +75,7 @@ namespace detail template auto swap_if(Float& x, Float& y, std::greater<>, utility::identity) noexcept - -> std::enable_if_t::value> + -> detail::enable_if_t::value> { Float dx = x; x = (std::max)(x, y); @@ -85,28 +85,28 @@ namespace detail #if CPPSORT_STD_IDENTITY_AVAILABLE template auto swap_if(Integer& x, Integer& y, std::less<> comp, std::identity) noexcept - -> std::enable_if_t::value> + -> detail::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> + -> detail::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> + -> detail::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> + -> detail::enable_if_t::value> { return swap_if(x, y, comp, utility::identity{}); } @@ -115,56 +115,56 @@ namespace detail #ifdef __cpp_lib_ranges template auto swap_if(Integer& x, Integer& y, std::ranges::less comp, utility::identity) noexcept - -> std::enable_if_t::value> + -> detail::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> + -> detail::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> + -> detail::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> + -> detail::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> + -> detail::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> + -> detail::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> + -> detail::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> + -> detail::enable_if_t::value> { return swap_if(x, y, comp, utility::identity{}); } @@ -177,7 +177,7 @@ namespace detail typename Iterator, typename Compare, typename Projection, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_detected_v || is_detected_v > @@ -198,7 +198,7 @@ namespace detail typename Iterator, typename Compare, typename Projection, - typename = std::enable_if_t< + typename = detail::enable_if_t< not is_detected_v && not is_detected_v >, diff --git a/include/cpp-sort/detail/type_traits.h b/include/cpp-sort/detail/type_traits.h index 45ef41f6..e2346398 100644 --- a/include/cpp-sort/detail/type_traits.h +++ b/include/cpp-sort/detail/type_traits.h @@ -16,7 +16,7 @@ namespace cppsort namespace detail { //////////////////////////////////////////////////////////// - // Alternative to std::conditional from C++11 + // Alternative to std::conditional_t from C++11 template struct conditional @@ -35,6 +35,22 @@ namespace detail template using conditional_t = typename conditional::template type; + //////////////////////////////////////////////////////////// + // Alternative to std::enable_if_t from C++11 + + template + struct enable_if_impl {}; + + template<> + struct enable_if_impl + { + template + using type = T; + }; + + template + using enable_if_t = typename enable_if_impl::template type; + //////////////////////////////////////////////////////////// // std::void_t from C++17 diff --git a/include/cpp-sort/fixed/low_comparisons_sorter.h b/include/cpp-sort/fixed/low_comparisons_sorter.h index fac933a0..be6b5726 100644 --- a/include/cpp-sort/fixed/low_comparisons_sorter.h +++ b/include/cpp-sort/fixed/low_comparisons_sorter.h @@ -72,6 +72,16 @@ namespace cppsort }; } +// Common includes for specializations +#include +#include +#include +#include +#include +#include "../detail/front_insert.h" +#include "../detail/swap_if.h" +#include "../detail/type_traits.h" + // Specializations of low_comparisons_sorter for some values of N #include "../detail/low_comparisons/sort2.h" #include "../detail/low_comparisons/sort3.h" diff --git a/include/cpp-sort/fixed/low_moves_sorter.h b/include/cpp-sort/fixed/low_moves_sorter.h index 4ab33554..0b460a2e 100644 --- a/include/cpp-sort/fixed/low_moves_sorter.h +++ b/include/cpp-sort/fixed/low_moves_sorter.h @@ -38,7 +38,7 @@ namespace cppsort typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/fixed/merge_exchange_network_sorter.h b/include/cpp-sort/fixed/merge_exchange_network_sorter.h index 3cbe9333..75957bbf 100644 --- a/include/cpp-sort/fixed/merge_exchange_network_sorter.h +++ b/include/cpp-sort/fixed/merge_exchange_network_sorter.h @@ -22,6 +22,7 @@ #include "../detail/empty_sorter.h" #include "../detail/iterator_traits.h" #include "../detail/make_array.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -100,7 +101,7 @@ namespace cppsort typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/fixed/odd_even_merge_network_sorter.h b/include/cpp-sort/fixed/odd_even_merge_network_sorter.h index 5415aa51..e7f8a128 100644 --- a/include/cpp-sort/fixed/odd_even_merge_network_sorter.h +++ b/include/cpp-sort/fixed/odd_even_merge_network_sorter.h @@ -22,6 +22,7 @@ #include "../detail/empty_sorter.h" #include "../detail/iterator_traits.h" #include "../detail/make_array.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -88,7 +89,7 @@ namespace cppsort typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/fixed/sorting_network_sorter.h b/include/cpp-sort/fixed/sorting_network_sorter.h index f0d26675..73de1c7b 100644 --- a/include/cpp-sort/fixed/sorting_network_sorter.h +++ b/include/cpp-sort/fixed/sorting_network_sorter.h @@ -78,6 +78,7 @@ namespace cppsort #include #include #include "../detail/swap_if.h" +#include "../detail/type_traits.h" // Specializations of sorting_network_sorter for some values of N #include "../detail/sorting_network/sort2.h" diff --git a/include/cpp-sort/probes/block.h b/include/cpp-sort/probes/block.h index 442fcdca..a66c64de 100644 --- a/include/cpp-sort/probes/block.h +++ b/include/cpp-sort/probes/block.h @@ -23,6 +23,7 @@ #include "../detail/immovable_vector.h" #include "../detail/iterator_traits.h" #include "../detail/pdqsort.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -87,7 +88,7 @@ namespace probe typename ForwardIterable, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = cppsort::detail::enable_if_t< is_projection_v > > @@ -104,7 +105,7 @@ namespace probe typename ForwardIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = cppsort::detail::enable_if_t< is_projection_iterator_v > > diff --git a/include/cpp-sort/probes/dis.h b/include/cpp-sort/probes/dis.h index 418753f1..952fde70 100644 --- a/include/cpp-sort/probes/dis.h +++ b/include/cpp-sort/probes/dis.h @@ -143,7 +143,7 @@ namespace probe typename ForwardIterable, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = cppsort::detail::enable_if_t< is_projection_v > > @@ -163,7 +163,7 @@ namespace probe typename ForwardIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = cppsort::detail::enable_if_t< is_projection_iterator_v > > diff --git a/include/cpp-sort/probes/enc.h b/include/cpp-sort/probes/enc.h index e0fc97ab..76cc4ff0 100644 --- a/include/cpp-sort/probes/enc.h +++ b/include/cpp-sort/probes/enc.h @@ -22,6 +22,7 @@ #include "../detail/functional.h" #include "../detail/iterator_traits.h" #include "../detail/lower_bound.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -64,7 +65,7 @@ namespace probe typename ForwardIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = cppsort::detail::enable_if_t< is_projection_iterator_v > > diff --git a/include/cpp-sort/probes/exc.h b/include/cpp-sort/probes/exc.h index 0dec911b..c97c76e8 100644 --- a/include/cpp-sort/probes/exc.h +++ b/include/cpp-sort/probes/exc.h @@ -23,6 +23,7 @@ #include "../detail/immovable_vector.h" #include "../detail/iterator_traits.h" #include "../detail/pdqsort.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -113,7 +114,7 @@ namespace probe typename ForwardIterable, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = cppsort::detail::enable_if_t< is_projection_v > > @@ -130,7 +131,7 @@ namespace probe typename ForwardIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = cppsort::detail::enable_if_t< is_projection_iterator_v > > diff --git a/include/cpp-sort/probes/ham.h b/include/cpp-sort/probes/ham.h index cbf17ad2..03ee0086 100644 --- a/include/cpp-sort/probes/ham.h +++ b/include/cpp-sort/probes/ham.h @@ -22,6 +22,7 @@ #include "../detail/immovable_vector.h" #include "../detail/iterator_traits.h" #include "../detail/pdqsort.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -78,7 +79,7 @@ namespace probe typename ForwardIterable, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = cppsort::detail::enable_if_t< is_projection_v > > @@ -95,7 +96,7 @@ namespace probe typename ForwardIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = cppsort::detail::enable_if_t< is_projection_iterator_v > > diff --git a/include/cpp-sort/probes/inv.h b/include/cpp-sort/probes/inv.h index 8158dd03..cf521125 100644 --- a/include/cpp-sort/probes/inv.h +++ b/include/cpp-sort/probes/inv.h @@ -21,6 +21,7 @@ #include "../detail/count_inversions.h" #include "../detail/functional.h" #include "../detail/iterator_traits.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -61,7 +62,7 @@ namespace probe typename ForwardIterable, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = cppsort::detail::enable_if_t< is_projection_v > > @@ -78,7 +79,7 @@ namespace probe typename ForwardIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = cppsort::detail::enable_if_t< is_projection_iterator_v > > diff --git a/include/cpp-sort/probes/max.h b/include/cpp-sort/probes/max.h index ab54d7db..94dbf57d 100644 --- a/include/cpp-sort/probes/max.h +++ b/include/cpp-sort/probes/max.h @@ -24,6 +24,7 @@ #include "../detail/immovable_vector.h" #include "../detail/iterator_traits.h" #include "../detail/pdqsort.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -92,7 +93,7 @@ namespace probe typename ForwardIterable, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = cppsort::detail::enable_if_t< is_projection_v > > @@ -109,7 +110,7 @@ namespace probe typename ForwardIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = cppsort::detail::enable_if_t< is_projection_iterator_v > > diff --git a/include/cpp-sort/probes/mono.h b/include/cpp-sort/probes/mono.h index 82b2daf4..ae311b70 100644 --- a/include/cpp-sort/probes/mono.h +++ b/include/cpp-sort/probes/mono.h @@ -17,6 +17,7 @@ #include #include #include "../detail/iterator_traits.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -30,7 +31,7 @@ namespace probe typename ForwardIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = cppsort::detail::enable_if_t< is_projection_iterator_v > > diff --git a/include/cpp-sort/probes/osc.h b/include/cpp-sort/probes/osc.h index 8c94dc40..b527b355 100644 --- a/include/cpp-sort/probes/osc.h +++ b/include/cpp-sort/probes/osc.h @@ -27,6 +27,7 @@ #include "../detail/immovable_vector.h" #include "../detail/iterator_traits.h" #include "../detail/pdqsort.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -179,7 +180,7 @@ namespace probe typename ForwardIterable, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = cppsort::detail::enable_if_t< is_projection_v > > @@ -195,7 +196,7 @@ namespace probe typename ForwardIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = cppsort::detail::enable_if_t< is_projection_iterator_v > > diff --git a/include/cpp-sort/probes/rem.h b/include/cpp-sort/probes/rem.h index d390b25c..cdd20d6f 100644 --- a/include/cpp-sort/probes/rem.h +++ b/include/cpp-sort/probes/rem.h @@ -18,6 +18,7 @@ #include #include #include "../detail/longest_non_descending_subsequence.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -31,7 +32,7 @@ namespace probe typename ForwardIterable, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = cppsort::detail::enable_if_t< is_projection_v && ( cppsort::detail::is_detected_v< cppsort::utility::detail::has_size_method_t, @@ -65,7 +66,7 @@ namespace probe typename ForwardIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = cppsort::detail::enable_if_t< is_projection_iterator_v > > diff --git a/include/cpp-sort/probes/runs.h b/include/cpp-sort/probes/runs.h index 444b7e58..6677f36f 100644 --- a/include/cpp-sort/probes/runs.h +++ b/include/cpp-sort/probes/runs.h @@ -17,6 +17,7 @@ #include #include #include "../detail/iterator_traits.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -30,7 +31,7 @@ namespace probe typename ForwardIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = cppsort::detail::enable_if_t< is_projection_iterator_v > > diff --git a/include/cpp-sort/probes/sus.h b/include/cpp-sort/probes/sus.h index 18d8e1de..4fbe0953 100644 --- a/include/cpp-sort/probes/sus.h +++ b/include/cpp-sort/probes/sus.h @@ -19,6 +19,7 @@ #include #include "../detail/functional.h" #include "../detail/longest_non_descending_subsequence.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -32,7 +33,7 @@ namespace probe typename ForwardIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = cppsort::detail::enable_if_t< is_projection_iterator_v > > diff --git a/include/cpp-sort/refined.h b/include/cpp-sort/refined.h index afa1145b..bb8d5122 100644 --- a/include/cpp-sort/refined.h +++ b/include/cpp-sort/refined.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2018 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_REFINED_H_ @@ -35,7 +35,7 @@ namespace cppsort template< typename T, typename Function, - typename = std::enable_if_t> + typename = detail::enable_if_t> > auto refined(Function func) noexcept(noexcept(func.template refine())) @@ -47,7 +47,7 @@ namespace cppsort template< typename T, typename Function, - typename = std::enable_if_t> + typename = detail::enable_if_t> > constexpr auto refined(Function&& func) noexcept -> Function&& diff --git a/include/cpp-sort/sort.h b/include/cpp-sort/sort.h index 688237f1..b98692f2 100644 --- a/include/cpp-sort/sort.h +++ b/include/cpp-sort/sort.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2020 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORT_H_ @@ -13,6 +13,7 @@ #include #include #include "detail/config.h" +#include "detail/type_traits.h" namespace cppsort { @@ -30,7 +31,7 @@ namespace cppsort template< typename Iterable, typename Compare, - typename = std::enable_if_t> + typename = detail::enable_if_t> > CPPSORT_DEPRECATED("cppsort::sort() is deprecated and will be removed in version 2.0.0") auto sort(Iterable&& iterable, Compare compare) @@ -43,7 +44,7 @@ namespace cppsort typename Iterable, typename Compare, typename Projection, - typename = std::enable_if_t< + typename = detail::enable_if_t< not is_comparison_sorter_v && not is_projection_sorter_v && not is_sorter_iterator_v && @@ -69,7 +70,7 @@ namespace cppsort template< typename Iterator, typename Func, - typename = std::enable_if_t> + typename = detail::enable_if_t> > CPPSORT_DEPRECATED("cppsort::sort() is deprecated and will be removed in version 2.0.0") auto sort(Iterator first, Iterator last, Func func) @@ -82,7 +83,7 @@ namespace cppsort typename Iterator, typename Compare, typename Projection, - typename = std::enable_if_t< + typename = detail::enable_if_t< not is_comparison_sorter_iterator_v && not is_projection_sorter_iterator_v > @@ -101,7 +102,7 @@ namespace cppsort template< typename Sorter, typename Iterable, - typename = std::enable_if_t> + typename = detail::enable_if_t> > CPPSORT_DEPRECATED("cppsort::sort() is deprecated and will be removed in version 2.0.0") auto sort(const Sorter& sorter, Iterable&& iterable) @@ -114,7 +115,7 @@ namespace cppsort typename Sorter, typename Iterable, typename Func, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_comparison_sorter_v || is_projection_sorter_v > @@ -144,7 +145,7 @@ namespace cppsort template< typename Sorter, typename Iterator, - typename = std::enable_if_t> + typename = detail::enable_if_t> > CPPSORT_DEPRECATED("cppsort::sort() is deprecated and will be removed in version 2.0.0") auto sort(const Sorter& sorter, Iterator first, Iterator last) @@ -157,7 +158,7 @@ namespace cppsort typename Sorter, typename Iterator, typename Func, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_comparison_sorter_iterator_v || is_projection_sorter_iterator_v > diff --git a/include/cpp-sort/sorter_facade.h b/include/cpp-sort/sorter_facade.h index ce65b9cb..f18c2b25 100644 --- a/include/cpp-sort/sorter_facade.h +++ b/include/cpp-sort/sorter_facade.h @@ -36,7 +36,7 @@ namespace cppsort typename... Args > static constexpr auto invoke(Args... args) - -> std::enable_if_t< + -> detail::enable_if_t< not std::is_void::value, Ret > @@ -49,7 +49,7 @@ namespace cppsort typename... Args > static constexpr auto invoke(Args... args) - -> std::enable_if_t< + -> detail::enable_if_t< std::is_void::value, void > @@ -175,7 +175,7 @@ namespace cppsort template constexpr auto operator()(Iterable&& iterable) const - -> std::enable_if_t< + -> detail::enable_if_t< detail::has_sort::value, decltype(Sorter::operator()(std::forward(iterable))) > @@ -185,7 +185,7 @@ namespace cppsort template constexpr auto operator()(Iterable&& iterable) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_sort::value, decltype(Sorter::operator()(std::begin(iterable), std::end(iterable))) > @@ -198,7 +198,7 @@ namespace cppsort template constexpr auto operator()(Iterator first, Iterator last, Compare compare) const - -> std::enable_if_t< + -> detail::enable_if_t< detail::has_comparison_sort_iterator< Sorter, Iterator, @@ -214,7 +214,7 @@ namespace cppsort template constexpr auto operator()(Iterable&& iterable, Compare compare) const - -> std::enable_if_t< + -> detail::enable_if_t< detail::has_comparison_sort< Sorter, Iterable, @@ -230,7 +230,7 @@ namespace cppsort template constexpr auto operator()(Iterable&& iterable, Compare compare) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_comparison_sort< Sorter, Iterable, @@ -254,7 +254,7 @@ namespace cppsort template constexpr auto operator()(Iterator first, Iterator last, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_comparison_sort_iterator< Sorter, Iterator, @@ -275,7 +275,7 @@ namespace cppsort template constexpr auto operator()(Iterator first, Iterator last, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_projection_sort_iterator< Sorter, Iterator, @@ -297,7 +297,7 @@ namespace cppsort template constexpr auto operator()(Iterable&& iterable, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_comparison_sort< Sorter, Iterable, @@ -318,7 +318,7 @@ namespace cppsort template constexpr auto operator()(Iterable&& iterable, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_comparison_sort< Sorter, Iterable, @@ -345,7 +345,7 @@ namespace cppsort template constexpr auto operator()(Iterable&& iterable, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_comparison_sort< Sorter, Iterable, @@ -382,7 +382,7 @@ namespace cppsort template constexpr auto operator()(Iterable&& iterable, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_projection_sort< Sorter, Iterable, @@ -418,7 +418,7 @@ namespace cppsort template constexpr auto operator()(Iterator first, Iterator last, std::less<>) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_comparison_sort_iterator>::value, decltype(Sorter::operator()(std::move(first), std::move(last))) > @@ -428,7 +428,7 @@ namespace cppsort template constexpr auto operator()(Iterable&& iterable, std::less<>) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_comparison_sort_iterator< Sorter, decltype(std::begin(iterable)), @@ -443,7 +443,7 @@ namespace cppsort #ifdef __cpp_lib_ranges template constexpr auto operator()(Iterator first, Iterator last, std::ranges::less) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_comparison_sort_iterator::value, decltype(Sorter::operator()(std::move(first), std::move(last))) > @@ -453,7 +453,7 @@ namespace cppsort template constexpr auto operator()(Iterable&& iterable, std::ranges::less) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_comparison_sort_iterator< Sorter, decltype(std::begin(iterable)), @@ -471,7 +471,7 @@ namespace cppsort template constexpr auto operator()(Iterator first, Iterator last, utility::identity) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_projection_sort_iterator::value && not detail::has_comparison_projection_sort_iterator< Sorter, @@ -487,7 +487,7 @@ namespace cppsort template constexpr auto operator()(Iterable&& iterable, utility::identity) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_projection_sort_iterator< Sorter, decltype(std::begin(iterable)), @@ -508,7 +508,7 @@ namespace cppsort #if CPPSORT_STD_IDENTITY_AVAILABLE template constexpr auto operator()(Iterator first, Iterator last, std::identity) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_projection_sort_iterator::value && not detail::has_comparison_projection_sort_iterator< Sorter, @@ -524,7 +524,7 @@ namespace cppsort template constexpr auto operator()(Iterable&& iterable, std::identity) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_projection_sort_iterator< Sorter, decltype(std::begin(iterable)), @@ -548,7 +548,7 @@ namespace cppsort template constexpr auto operator()(Iterator first, Iterator last, Compare compare, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< detail::has_comparison_projection_sort_iterator< Sorter, Iterator, @@ -567,7 +567,7 @@ namespace cppsort template constexpr auto operator()(Iterable&& iterable, Compare compare, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< detail::has_comparison_projection_sort< Sorter, Iterable, @@ -586,7 +586,7 @@ namespace cppsort template constexpr auto operator()(Iterable&& iterable, Compare compare, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_comparison_projection_sort< Sorter, Iterable, @@ -614,7 +614,7 @@ namespace cppsort template constexpr auto operator()(Iterator first, Iterator last, std::less<>, utility::identity) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_comparison_projection_sort_iterator< Sorter, Iterator, @@ -629,7 +629,7 @@ namespace cppsort template constexpr auto operator()(Iterable&& iterable, std::less<>, utility::identity) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_comparison_projection_sort_iterator< Sorter, decltype(std::begin(iterable)), @@ -645,7 +645,7 @@ namespace cppsort #if CPPSORT_STD_IDENTITY_AVAILABLE template constexpr auto operator()(Iterator first, Iterator last, std::less<>, std::identity) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_comparison_projection_sort_iterator< Sorter, Iterator, @@ -660,7 +660,7 @@ namespace cppsort template constexpr auto operator()(Iterable&& iterable, std::less<>, std::identity) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_comparison_projection_sort_iterator< Sorter, decltype(std::begin(iterable)), @@ -676,7 +676,7 @@ namespace cppsort template constexpr auto operator()(Iterator first, Iterator last, std::less<> compare, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< detail::has_comparison_projection_sort_iterator< Sorter, Iterator, @@ -693,7 +693,7 @@ namespace cppsort template constexpr auto operator()(Iterator first, Iterator last, std::less<>, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_comparison_projection_sort_iterator< Sorter, Iterator, @@ -715,7 +715,7 @@ namespace cppsort template constexpr auto operator()(Iterable&& iterable, std::less<> compare, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< detail::has_comparison_projection_sort< Sorter, Iterable, @@ -732,7 +732,7 @@ namespace cppsort template constexpr auto operator()(Iterable&& iterable, std::less<> compare, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_comparison_projection_sort< Sorter, Iterable, @@ -755,7 +755,7 @@ namespace cppsort template constexpr auto operator()(Iterable&& iterable, std::less<>, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_comparison_projection_sort< Sorter, Iterable, @@ -783,7 +783,7 @@ namespace cppsort template constexpr auto operator()(Iterable&& iterable, std::less<>, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_comparison_projection_sort< Sorter, Iterable, @@ -817,7 +817,7 @@ namespace cppsort #ifdef __cpp_lib_ranges template constexpr auto operator()(Iterator first, Iterator last, std::ranges::less, std::identity) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_comparison_projection_sort_iterator< Sorter, Iterator, @@ -832,7 +832,7 @@ namespace cppsort template constexpr auto operator()(Iterable&& iterable, std::ranges::less, std::identity) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_comparison_projection_sort_iterator< Sorter, decltype(std::begin(iterable)), @@ -847,7 +847,7 @@ namespace cppsort template constexpr auto operator()(Iterator first, Iterator last, std::ranges::less compare, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< detail::has_comparison_projection_sort_iterator< Sorter, Iterator, @@ -864,7 +864,7 @@ namespace cppsort template constexpr auto operator()(Iterator first, Iterator last, std::ranges::less, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_comparison_projection_sort_iterator< Sorter, Iterator, @@ -886,7 +886,7 @@ namespace cppsort template constexpr auto operator()(Iterable&& iterable, std::ranges::less compare, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< detail::has_comparison_projection_sort< Sorter, Iterable, @@ -903,7 +903,7 @@ namespace cppsort template constexpr auto operator()(Iterable&& iterable, std::ranges::less compare, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_comparison_projection_sort< Sorter, Iterable, @@ -926,7 +926,7 @@ namespace cppsort template constexpr auto operator()(Iterable&& iterable, std::ranges::less, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_comparison_projection_sort< Sorter, Iterable, @@ -954,7 +954,7 @@ namespace cppsort template constexpr auto operator()(Iterable&& iterable, std::ranges::less, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_comparison_projection_sort< Sorter, Iterable, @@ -991,7 +991,7 @@ namespace cppsort template constexpr auto operator()(Iterator first, Iterator last, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_projection_sort_iterator::value && not detail::has_comparison_projection_sort_iterator< Sorter, @@ -1016,7 +1016,7 @@ namespace cppsort template constexpr auto operator()(Iterator first, Iterator last, Compare compare, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_comparison_projection_sort_iterator< Sorter, Iterator, @@ -1043,7 +1043,7 @@ namespace cppsort template constexpr auto operator()(Iterable&& iterable, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_projection_sort_iterator< Sorter, decltype(std::begin(iterable)), @@ -1073,7 +1073,7 @@ namespace cppsort template constexpr auto operator()(Iterable&& iterable, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_projection_sort_iterator< Sorter, decltype(std::begin(iterable)), @@ -1113,7 +1113,7 @@ namespace cppsort template constexpr auto operator()(Iterable&& iterable, Compare compare, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_comparison_projection_sort_iterator< Sorter, decltype(std::begin(iterable)), @@ -1140,7 +1140,7 @@ namespace cppsort template constexpr auto operator()(Iterable&& iterable, Compare compare, Projection projection) const - -> std::enable_if_t< + -> detail::enable_if_t< not detail::has_comparison_projection_sort_iterator< Sorter, decltype(std::begin(iterable)), diff --git a/include/cpp-sort/sorters/block_sorter.h b/include/cpp-sort/sorters/block_sorter.h index 93ebad60..e8302b54 100644 --- a/include/cpp-sort/sorters/block_sorter.h +++ b/include/cpp-sort/sorters/block_sorter.h @@ -19,6 +19,7 @@ #include #include "../detail/attributes.h" #include "../detail/iterator_traits.h" +#include "../detail/type_traits.h" #include "../detail/wiki_sort.h" namespace cppsort @@ -35,7 +36,7 @@ namespace cppsort typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/sorters/cartesian_tree_sorter.h b/include/cpp-sort/sorters/cartesian_tree_sorter.h index df275ff6..ebc20035 100644 --- a/include/cpp-sort/sorters/cartesian_tree_sorter.h +++ b/include/cpp-sort/sorters/cartesian_tree_sorter.h @@ -19,6 +19,7 @@ #include #include "../detail/cartesian_tree_sort.h" #include "../detail/iterator_traits.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -33,7 +34,7 @@ namespace cppsort typename ForwardIterable, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_v > > @@ -58,7 +59,7 @@ namespace cppsort typename ForwardIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_iterator_v > > diff --git a/include/cpp-sort/sorters/counting_sorter.h b/include/cpp-sort/sorters/counting_sorter.h index ec00aa09..d537e3b0 100644 --- a/include/cpp-sort/sorters/counting_sorter.h +++ b/include/cpp-sort/sorters/counting_sorter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2020 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORTERS_COUNTING_SORTER_H_ @@ -29,7 +29,7 @@ namespace cppsort { template auto operator()(ForwardIterator first, ForwardIterator last) const - -> std::enable_if_t< + -> detail::enable_if_t< detail::is_integral>::value > { @@ -46,7 +46,7 @@ namespace cppsort template auto operator()(ForwardIterator first, ForwardIterator last, std::greater<>) const - -> std::enable_if_t< + -> detail::enable_if_t< detail::is_integral>::value > { @@ -64,7 +64,7 @@ namespace cppsort #ifdef __cpp_lib_ranges template auto operator()(ForwardIterator first, ForwardIterator last, std::ranges::greater) const - -> std::enable_if_t< + -> detail::enable_if_t< detail::is_integral>::value > { diff --git a/include/cpp-sort/sorters/drop_merge_sorter.h b/include/cpp-sort/sorters/drop_merge_sorter.h index 991e5131..aee36daf 100644 --- a/include/cpp-sort/sorters/drop_merge_sorter.h +++ b/include/cpp-sort/sorters/drop_merge_sorter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017 Morwenn + * Copyright (c) 2017-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORTERS_DROP_MERGE_SORTER_H_ @@ -18,6 +18,7 @@ #include #include "../detail/drop_merge_sort.h" #include "../detail/iterator_traits.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -32,7 +33,7 @@ namespace cppsort typename BidirectionalIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_iterator_v > > diff --git a/include/cpp-sort/sorters/grail_sorter.h b/include/cpp-sort/sorters/grail_sorter.h index 46f82716..4842bced 100644 --- a/include/cpp-sort/sorters/grail_sorter.h +++ b/include/cpp-sort/sorters/grail_sorter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2019 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORTERS_GRAIL_SORTER_H_ @@ -18,6 +18,7 @@ #include #include #include "../detail/grail_sort.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -33,7 +34,7 @@ namespace cppsort typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/sorters/heap_sorter.h b/include/cpp-sort/sorters/heap_sorter.h index 76a94f14..783c8747 100644 --- a/include/cpp-sort/sorters/heap_sorter.h +++ b/include/cpp-sort/sorters/heap_sorter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2016 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORTERS_HEAP_SORTER_H_ @@ -18,6 +18,7 @@ #include #include "../detail/heapsort.h" #include "../detail/iterator_traits.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -32,7 +33,7 @@ namespace cppsort typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_iterator_v > > diff --git a/include/cpp-sort/sorters/insertion_sorter.h b/include/cpp-sort/sorters/insertion_sorter.h index a491ad37..c5aa074d 100644 --- a/include/cpp-sort/sorters/insertion_sorter.h +++ b/include/cpp-sort/sorters/insertion_sorter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2017 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORTERS_INSERTION_SORTER_H_ @@ -18,6 +18,7 @@ #include #include "../detail/insertion_sort.h" #include "../detail/iterator_traits.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -32,7 +33,7 @@ namespace cppsort typename BidirectionalIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_iterator_v > > diff --git a/include/cpp-sort/sorters/mel_sorter.h b/include/cpp-sort/sorters/mel_sorter.h index 51746c75..b52628e0 100644 --- a/include/cpp-sort/sorters/mel_sorter.h +++ b/include/cpp-sort/sorters/mel_sorter.h @@ -19,6 +19,7 @@ #include #include "../detail/iterator_traits.h" #include "../detail/melsort.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -33,7 +34,7 @@ namespace cppsort typename ForwardIterable, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_v > > @@ -58,7 +59,7 @@ namespace cppsort typename ForwardIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_iterator_v > > diff --git a/include/cpp-sort/sorters/merge_insertion_sorter.h b/include/cpp-sort/sorters/merge_insertion_sorter.h index 8d578195..fc7ddb99 100644 --- a/include/cpp-sort/sorters/merge_insertion_sorter.h +++ b/include/cpp-sort/sorters/merge_insertion_sorter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORTERS_MERGE_INSERTION_SORTER_H_ @@ -18,6 +18,7 @@ #include #include "../detail/iterator_traits.h" #include "../detail/merge_insertion_sort.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -32,7 +33,7 @@ namespace cppsort typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/sorters/merge_sorter.h b/include/cpp-sort/sorters/merge_sorter.h index f5b7b1cc..37f30ad3 100644 --- a/include/cpp-sort/sorters/merge_sorter.h +++ b/include/cpp-sort/sorters/merge_sorter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2017 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORTERS_MERGE_SORTER_H_ @@ -19,6 +19,7 @@ #include #include "../detail/iterator_traits.h" #include "../detail/merge_sort.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -33,7 +34,7 @@ namespace cppsort typename ForwardIterable, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_v > > @@ -58,7 +59,7 @@ namespace cppsort typename ForwardIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_iterator_v > > diff --git a/include/cpp-sort/sorters/pdq_sorter.h b/include/cpp-sort/sorters/pdq_sorter.h index bfdb0237..2a404af7 100644 --- a/include/cpp-sort/sorters/pdq_sorter.h +++ b/include/cpp-sort/sorters/pdq_sorter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2016 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORTERS_PDQ_SORTER_H_ @@ -18,6 +18,7 @@ #include #include "../detail/iterator_traits.h" #include "../detail/pdqsort.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -32,7 +33,7 @@ namespace cppsort typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_iterator_v > > diff --git a/include/cpp-sort/sorters/poplar_sorter.h b/include/cpp-sort/sorters/poplar_sorter.h index 956cca2d..bf6d4f6b 100644 --- a/include/cpp-sort/sorters/poplar_sorter.h +++ b/include/cpp-sort/sorters/poplar_sorter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORTERS_POPLAR_SORTER_H_ @@ -18,6 +18,7 @@ #include #include "../detail/iterator_traits.h" #include "../detail/poplar_sort.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -32,7 +33,7 @@ namespace cppsort typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_iterator_v > > diff --git a/include/cpp-sort/sorters/quick_merge_sorter.h b/include/cpp-sort/sorters/quick_merge_sorter.h index 49aed9dd..1314672b 100644 --- a/include/cpp-sort/sorters/quick_merge_sorter.h +++ b/include/cpp-sort/sorters/quick_merge_sorter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2020 Morwenn + * Copyright (c) 2018-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORTERS_QUICK_MERGE_SORTER_H_ @@ -19,6 +19,7 @@ #include #include "../detail/iterator_traits.h" #include "../detail/quick_merge_sort.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -33,7 +34,7 @@ namespace cppsort typename ForwardIterable, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_v > > @@ -58,7 +59,7 @@ namespace cppsort typename ForwardIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_iterator_v > > diff --git a/include/cpp-sort/sorters/quick_sorter.h b/include/cpp-sort/sorters/quick_sorter.h index 37b3808e..16cebdc6 100644 --- a/include/cpp-sort/sorters/quick_sorter.h +++ b/include/cpp-sort/sorters/quick_sorter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2017 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORTERS_QUICK_SORTER_H_ @@ -19,6 +19,7 @@ #include #include "../detail/iterator_traits.h" #include "../detail/quicksort.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -33,7 +34,7 @@ namespace cppsort typename ForwardIterable, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_v > > @@ -58,7 +59,7 @@ namespace cppsort typename ForwardIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_iterator_v > > diff --git a/include/cpp-sort/sorters/selection_sorter.h b/include/cpp-sort/sorters/selection_sorter.h index 7502cb24..7aca171f 100644 --- a/include/cpp-sort/sorters/selection_sorter.h +++ b/include/cpp-sort/sorters/selection_sorter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2016 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORTERS_SELECTION_SORTER_H_ @@ -18,6 +18,7 @@ #include #include "../detail/iterator_traits.h" #include "../detail/selection_sort.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -32,7 +33,7 @@ namespace cppsort typename ForwardIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_iterator_v > > diff --git a/include/cpp-sort/sorters/ska_sorter.h b/include/cpp-sort/sorters/ska_sorter.h index 559831c3..fa39e96d 100644 --- a/include/cpp-sort/sorters/ska_sorter.h +++ b/include/cpp-sort/sorters/ska_sorter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2018 Morwenn + * Copyright (c) 2017-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORTERS_SKA_SORTER_H_ @@ -32,13 +32,13 @@ namespace cppsort template< typename RandomAccessIterator, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_iterator_v > > auto operator()(RandomAccessIterator first, RandomAccessIterator last, Projection projection={}) const - -> std::enable_if_t detail::enable_if_t >> { diff --git a/include/cpp-sort/sorters/slab_sorter.h b/include/cpp-sort/sorters/slab_sorter.h index 833b289b..760b03b7 100644 --- a/include/cpp-sort/sorters/slab_sorter.h +++ b/include/cpp-sort/sorters/slab_sorter.h @@ -18,6 +18,7 @@ #include #include "../detail/iterator_traits.h" #include "../detail/slabsort.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -32,7 +33,7 @@ namespace cppsort typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_iterator_v > > diff --git a/include/cpp-sort/sorters/smooth_sorter.h b/include/cpp-sort/sorters/smooth_sorter.h index 3b02c5ae..6afb4157 100644 --- a/include/cpp-sort/sorters/smooth_sorter.h +++ b/include/cpp-sort/sorters/smooth_sorter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2016 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORTERS_SMOOTH_SORTER_H_ @@ -18,6 +18,7 @@ #include #include "../detail/iterator_traits.h" #include "../detail/smoothsort.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -32,7 +33,7 @@ namespace cppsort typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/sorters/spin_sorter.h b/include/cpp-sort/sorters/spin_sorter.h index 0281835c..71b513e6 100644 --- a/include/cpp-sort/sorters/spin_sorter.h +++ b/include/cpp-sort/sorters/spin_sorter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Morwenn + * Copyright (c) 2019-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORTERS_SPIN_SORTER_H_ @@ -18,6 +18,7 @@ #include #include "../detail/iterator_traits.h" #include "../detail/spinsort.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -32,7 +33,7 @@ namespace cppsort typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_iterator_v > > diff --git a/include/cpp-sort/sorters/split_sorter.h b/include/cpp-sort/sorters/split_sorter.h index b0169cd6..6db5bc83 100644 --- a/include/cpp-sort/sorters/split_sorter.h +++ b/include/cpp-sort/sorters/split_sorter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Morwenn + * Copyright (c) 2019-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORTERS_SPLIT_SORTER_H_ @@ -18,6 +18,7 @@ #include #include "../detail/iterator_traits.h" #include "../detail/split_sort.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -32,7 +33,7 @@ namespace cppsort typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_iterator_v > > 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 8873fb74..8a7b341d 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-2020 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORTERS_SPREAD_SORTER_FLOAT_SPREAD_SORTER_H_ @@ -19,6 +19,7 @@ #include #include "../../detail/iterator_traits.h" #include "../../detail/spreadsort/float_sort.h" +#include "../../detail/type_traits.h" namespace cppsort { @@ -35,7 +36,7 @@ namespace cppsort > auto operator()(RandomAccessIterator first, RandomAccessIterator last, Projection projection={}) const - -> std::enable_if_t< + -> detail::enable_if_t< std::numeric_limits>::is_iec559 && ( sizeof(projected_t) == sizeof(std::uint32_t) || sizeof(projected_t) == sizeof(std::uint64_t) diff --git a/include/cpp-sort/sorters/spread_sorter/integer_spread_sorter.h b/include/cpp-sort/sorters/spread_sorter/integer_spread_sorter.h index 3cd1fbfc..9f5a3513 100644 --- a/include/cpp-sort/sorters/spread_sorter/integer_spread_sorter.h +++ b/include/cpp-sort/sorters/spread_sorter/integer_spread_sorter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2017 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORTERS_SPREAD_SORTER_INTEGER_SPREAD_SORTER_H_ @@ -19,6 +19,7 @@ #include #include "../../detail/iterator_traits.h" #include "../../detail/spreadsort/integer_sort.h" +#include "../../detail/type_traits.h" namespace cppsort { @@ -35,7 +36,7 @@ namespace cppsort > auto operator()(RandomAccessIterator first, RandomAccessIterator last, Projection projection={}) const - -> std::enable_if_t< + -> detail::enable_if_t< std::is_integral>::value && ( sizeof(projected_t) <= sizeof(std::size_t) || sizeof(projected_t) <= sizeof(std::uintmax_t) 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 aac66f71..733df4a9 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-2020 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORTERS_SPREAD_SORTER_STRING_SPREAD_SORTER_H_ @@ -20,6 +20,7 @@ #include "../../detail/config.h" #include "../../detail/iterator_traits.h" #include "../../detail/spreadsort/string_sort.h" +#include "../../detail/type_traits.h" #if __cplusplus > 201402L && __has_include() # include @@ -43,7 +44,7 @@ namespace cppsort > auto operator()(RandomAccessIterator first, RandomAccessIterator last, Projection projection={}) const - -> std::enable_if_t< + -> detail::enable_if_t< std::is_same, std::string>::value #if __cplusplus > 201402L && __has_include() || std::is_same, std::string_view>::value @@ -69,7 +70,7 @@ namespace cppsort > auto operator()(RandomAccessIterator first, RandomAccessIterator last, Projection projection={}) const - -> std::enable_if_t<( + -> detail::enable_if_t<( std::is_same, std::wstring>::value #if __cplusplus > 201402L && __has_include() || std::is_same, std::wstring_view>::value @@ -99,7 +100,7 @@ namespace cppsort > auto operator()(RandomAccessIterator first, RandomAccessIterator last, std::greater<> compare, Projection projection={}) const - -> std::enable_if_t< + -> detail::enable_if_t< std::is_same, std::string>::value #if __cplusplus > 201402L && __has_include() || std::is_same, std::string_view>::value @@ -126,7 +127,7 @@ namespace cppsort > auto operator()(RandomAccessIterator first, RandomAccessIterator last, std::greater<> compare, Projection projection={}) const - -> std::enable_if_t<( + -> detail::enable_if_t<( std::is_same, std::wstring>::value #if __cplusplus > 201402L && __has_include() || std::is_same, std::wstring_view>::value @@ -155,7 +156,7 @@ namespace cppsort > auto operator()(RandomAccessIterator first, RandomAccessIterator last, std::ranges::greater compare, Projection projection={}) const - -> std::enable_if_t< + -> detail::enable_if_t< std::is_same_v, std::string> || std::is_same_v, std::string_view> > @@ -180,7 +181,7 @@ namespace cppsort > auto operator()(RandomAccessIterator first, RandomAccessIterator last, std::ranges::greater compare, Projection projection={}) const - -> std::enable_if_t<( + -> detail::enable_if_t<( std::is_same_v, std::wstring> || std::is_same_v, std::wstring_view> ) && (sizeof(wchar_t) == 2) diff --git a/include/cpp-sort/sorters/std_sorter.h b/include/cpp-sort/sorters/std_sorter.h index 91c0cbe1..5e9d1ade 100644 --- a/include/cpp-sort/sorters/std_sorter.h +++ b/include/cpp-sort/sorters/std_sorter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2020 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORTERS_STD_SORTER_H_ @@ -19,6 +19,7 @@ #include #include #include "../detail/iterator_traits.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -32,7 +33,7 @@ namespace cppsort template< typename RandomAccessIterator, typename Compare = std::less<>, - typename = std::enable_if_t> > @@ -74,7 +75,7 @@ namespace cppsort template< typename RandomAccessIterator, typename Compare = std::less<>, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/sorters/tim_sorter.h b/include/cpp-sort/sorters/tim_sorter.h index 7193369d..1afbeddb 100644 --- a/include/cpp-sort/sorters/tim_sorter.h +++ b/include/cpp-sort/sorters/tim_sorter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2016 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORTERS_TIM_SORTER_H_ @@ -18,6 +18,7 @@ #include #include "../detail/iterator_traits.h" #include "../detail/timsort.h" +#include "../detail/type_traits.h" namespace cppsort { @@ -32,7 +33,7 @@ namespace cppsort typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_iterator_v > > diff --git a/include/cpp-sort/sorters/verge_sorter.h b/include/cpp-sort/sorters/verge_sorter.h index 7869c1ba..94bce6d3 100644 --- a/include/cpp-sort/sorters/verge_sorter.h +++ b/include/cpp-sort/sorters/verge_sorter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2020 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORTERS_VERGE_SORTER_H_ @@ -20,6 +20,7 @@ #include #include #include "../detail/iterator_traits.h" +#include "../detail/type_traits.h" #include "../detail/vergesort.h" namespace cppsort @@ -36,7 +37,7 @@ namespace cppsort typename BidirectionalIterable, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_v > > @@ -61,7 +62,7 @@ namespace cppsort typename BidirectionalIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_projection_iterator_v > > diff --git a/include/cpp-sort/sorters/wiki_sorter.h b/include/cpp-sort/sorters/wiki_sorter.h index 45270d87..b70f4d79 100644 --- a/include/cpp-sort/sorters/wiki_sorter.h +++ b/include/cpp-sort/sorters/wiki_sorter.h @@ -18,6 +18,7 @@ #include #include #include "../detail/iterator_traits.h" +#include "../detail/type_traits.h" #include "../detail/wiki_sort.h" namespace cppsort @@ -34,7 +35,7 @@ namespace cppsort typename RandomAccessIterator, typename Compare = std::less<>, typename Projection = utility::identity, - typename = std::enable_if_t> > diff --git a/include/cpp-sort/stable_sort.h b/include/cpp-sort/stable_sort.h index b3df6508..0265046f 100644 --- a/include/cpp-sort/stable_sort.h +++ b/include/cpp-sort/stable_sort.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2020 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_STABLE_SORT_H_ @@ -32,7 +32,7 @@ namespace cppsort template< typename Iterable, typename Compare, - typename = std::enable_if_t> + typename = detail::enable_if_t> > CPPSORT_DEPRECATED("cppsort::stable_sort() is deprecated and will be removed in version 2.0.0") auto stable_sort(Iterable&& iterable, Compare compare) @@ -45,7 +45,7 @@ namespace cppsort typename Iterable, typename Compare, typename Projection, - typename = std::enable_if_t< + typename = detail::enable_if_t< not is_comparison_sorter_v && not is_projection_sorter_v > @@ -69,7 +69,7 @@ namespace cppsort template< typename Iterator, typename Compare, - typename = std::enable_if_t> + typename = detail::enable_if_t> > CPPSORT_DEPRECATED("cppsort::stable_sort() is deprecated and will be removed in version 2.0.0") auto stable_sort(Iterator first, Iterator last, Compare compare) @@ -82,7 +82,7 @@ namespace cppsort typename Iterator, typename Compare, typename Projection, - typename = std::enable_if_t< + typename = detail::enable_if_t< not is_comparison_sorter_iterator_v && not is_projection_sorter_iterator_v > @@ -101,7 +101,7 @@ namespace cppsort template< typename Sorter, typename Iterable, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_sorter_v > > @@ -116,7 +116,7 @@ namespace cppsort typename Sorter, typename Iterable, typename Func, - typename = std::enable_if_t, is_projection_sorter >::value> @@ -146,7 +146,7 @@ namespace cppsort template< typename Sorter, typename Iterator, - typename = std::enable_if_t, Iterator >> > @@ -161,7 +161,7 @@ namespace cppsort typename Sorter, typename Iterator, typename Func, - typename = std::enable_if_t< + typename = detail::enable_if_t< is_comparison_sorter_iterator_v, Iterator, Func> || is_projection_sorter_iterator_v, Iterator, Func> > diff --git a/include/cpp-sort/utility/as_function.h b/include/cpp-sort/utility/as_function.h index 7a151d72..6b88bab1 100644 --- a/include/cpp-sort/utility/as_function.h +++ b/include/cpp-sort/utility/as_function.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2018 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ @@ -37,7 +37,7 @@ namespace utility template constexpr auto operator()(T&& t) const noexcept(noexcept(std::mem_fn(t))) - -> std::enable_if_t< + -> cppsort::detail::enable_if_t< std::is_member_pointer>::value, decltype(std::mem_fn(t)) > @@ -48,7 +48,7 @@ namespace utility template constexpr auto operator()(T && t) const noexcept(std::is_nothrow_constructible::value) - -> std::enable_if_t< + -> cppsort::detail::enable_if_t< not std::is_member_pointer>::value, T > diff --git a/include/cpp-sort/utility/functional.h b/include/cpp-sort/utility/functional.h index 1ed89129..013bb54f 100644 --- a/include/cpp-sort/utility/functional.h +++ b/include/cpp-sort/utility/functional.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2020 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_UTILITY_FUNCTIONAL_H_ @@ -61,7 +61,7 @@ namespace utility template< typename T, typename U, - typename = std::enable_if_t< + typename = cppsort::detail::enable_if_t< std::is_base_of>::value || std::is_base_of>::value > @@ -120,7 +120,7 @@ namespace utility template< typename Func, - typename = std::enable_if_t< + typename = cppsort::detail::enable_if_t< not std::is_same, as_projection_fn>::value > > @@ -186,7 +186,7 @@ namespace utility template< typename Func, - typename = std::enable_if_t< + typename = cppsort::detail::enable_if_t< not std::is_same, as_comparison_fn>::value > > @@ -240,7 +240,7 @@ namespace utility template constexpr auto as_projection(Function&& func) - -> std::enable_if_t< + -> cppsort::detail::enable_if_t< not detail::is_as_projection_fn>::value, detail::as_projection_fn> > @@ -250,7 +250,7 @@ namespace utility template constexpr auto as_projection(Function&& func) - -> std::enable_if_t< + -> cppsort::detail::enable_if_t< detail::is_as_projection_fn>::value, decltype(std::forward(func)) > @@ -260,7 +260,7 @@ namespace utility template constexpr auto as_comparison(Function&& func) - -> std::enable_if_t< + -> cppsort::detail::enable_if_t< not detail::is_as_comparison_fn>::value, detail::as_comparison_fn> > @@ -270,7 +270,7 @@ namespace utility template constexpr auto as_comparison(Function&& func) - -> std::enable_if_t< + -> cppsort::detail::enable_if_t< detail::is_as_comparison_fn>::value, decltype(std::forward(func)) > diff --git a/include/cpp-sort/utility/iter_move.h b/include/cpp-sort/utility/iter_move.h index 8dcb6ae3..c6c377a1 100644 --- a/include/cpp-sort/utility/iter_move.h +++ b/include/cpp-sort/utility/iter_move.h @@ -40,7 +40,7 @@ namespace utility template< typename Iterator, - typename = std::enable_if_t< + typename = cppsort::detail::enable_if_t< cppsort::detail::is_detected_v > > @@ -54,7 +54,7 @@ namespace utility template< typename Iterator, - typename = std::enable_if_t< + typename = cppsort::detail::enable_if_t< not cppsort::detail::is_detected_v >, typename = void // dummy parameter for ODR diff --git a/include/cpp-sort/utility/size.h b/include/cpp-sort/utility/size.h index 6045e745..9f40460e 100644 --- a/include/cpp-sort/utility/size.h +++ b/include/cpp-sort/utility/size.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2018 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_UTILITY_SIZE_H_ @@ -26,7 +26,7 @@ namespace utility template< typename Iterable, - typename = std::enable_if_t< + typename = cppsort::detail::enable_if_t< cppsort::detail::is_detected_v > > @@ -38,7 +38,7 @@ namespace utility template< typename Iterable, - typename = std::enable_if_t< + typename = cppsort::detail::enable_if_t< not cppsort::detail::is_detected_v > > From 8c51c1bc0eb8dfa8bed2d2888fdb4124fb6fa2cd Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 9 Sep 2021 16:22:15 +0200 Subject: [PATCH 21/79] Remove remnants of Valgrind support on MacOS --- testsuite/CMakeLists.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/testsuite/CMakeLists.txt b/testsuite/CMakeLists.txt index 4c935a9b..f67405d0 100644 --- a/testsuite/CMakeLists.txt +++ b/testsuite/CMakeLists.txt @@ -252,9 +252,6 @@ endif() 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) - set(MEMORYCHECK_SUPPRESSIONS_FILE ${CMAKE_CURRENT_SOURCE_DIR}/valgrind-osx.supp) - endif() endif() ######################################## From 01e40e111054805d079e6bd30c6f603697d82150 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Fri, 10 Sep 2021 00:11:34 +0200 Subject: [PATCH 22/79] Fix Windows Builds dabge in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 43f0d3a1..4f3d5f0a 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ 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) +![Windows builds status](https://github.com/Morwenn/cpp-sort/workflows/MSVC%20Builds/badge.svg?branch=develop) ![MacOS builds status](https://github.com/Morwenn/cpp-sort/workflows/MacOS%20Builds/badge.svg?branch=develop) **cpp-sort** requires C++14 support, and should work with the following compilers: From f867dab4278aeb82bbc965049b52fac5f16d61de Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 16 Sep 2021 10:17:03 +0200 Subject: [PATCH 23/79] CI: bump Ubuntu version from 16.04 to 18.04 --- .github/workflows/build-ubuntu.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index 2f8223d2..9c391156 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -23,7 +23,7 @@ on: jobs: build: - runs-on: ubuntu-16.04 + runs-on: ubuntu-18.04 strategy: fail-fast: false @@ -45,6 +45,10 @@ jobs: steps: - uses: actions/checkout@v2 + - name: Install GCC + if: ${{matrix.cxx == 'g++-5'}} + run: sudo apt-get install -y g++-5 + - name: Install Clang if: ${{matrix.cxx == 'clang++-6.0'}} run: sudo apt-get install -y clang-6.0 lld-6.0 From e40fb0a23938f9fc14d6ea25b411554ae51ab29b Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 16 Sep 2021 16:40:44 +0200 Subject: [PATCH 24/79] Bump downloaded Catch2 version to v2.13.7 --- testsuite/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testsuite/CMakeLists.txt b/testsuite/CMakeLists.txt index f67405d0..a82a5a80 100644 --- a/testsuite/CMakeLists.txt +++ b/testsuite/CMakeLists.txt @@ -31,7 +31,7 @@ else() message(STATUS "Catch2 not found") download_project(PROJ Catch2 GIT_REPOSITORY https://github.com/catchorg/Catch2 - GIT_TAG v2.13.6 + GIT_TAG v2.13.7 UPDATE_DISCONNECTED 1 ) add_subdirectory(${Catch2_SOURCE_DIR} ${Catch2_BINARY_DIR}) From 48f611ddf05a1d85fcdb63774cef3657a2a35174 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 4 Oct 2021 13:51:11 +0200 Subject: [PATCH 25/79] Don't templatize Wiki::sort on BufferProvider This would produce different template instantiations for every variation of fixed_buffer and dynamic_buffer, while they all provide a pointer and a size, which is all Wiki::sort cares about. Hoisting the buffer instantiation to the top-level wiki_sort function cuts the number of instantiations, which notably shrinks the size of the test suite by 2%. --- include/cpp-sort/detail/wiki_sort.h | 62 ++++++++++++++--------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/include/cpp-sort/detail/wiki_sort.h b/include/cpp-sort/detail/wiki_sort.h index fc3e5cfb..23fa5e79 100644 --- a/include/cpp-sort/detail/wiki_sort.h +++ b/include/cpp-sort/detail/wiki_sort.h @@ -327,15 +327,15 @@ namespace detail } }; - // bottom-up merge sort combined with an in-place merge algorithm for O(1) memory use - template auto sort(RandomAccessIterator first, RandomAccessIterator last, + CacheIterator cache_begin, difference_type_t cache_size, Compare compare, Projection projection) -> void { using utility::iter_swap; - using rvalue_type = rvalue_type_t; using difference_type = difference_type_t; difference_type size = last - first; @@ -406,16 +406,7 @@ namespace detail } } - // use a small cache to speed up some of the operations - // just keep in mind that making it too small ruins the point (nothing will fit into it), - // and making it too large also ruins the point (so much for "low memory"!) - typename BufferProvider::template buffer cache(size); - difference_type cache_size = cache.size(); - - // may be pointer of something else - using cache_iterator = decltype(cache.begin()); - - // then merge sort the higher levels, which can be 8-15, 16-31, 32-63, 64-127, etc. + // merge sort the higher levels, which can be 8-15, 16-31, 32-63, 64-127, etc. while (true) { // if every A and B block will fit into the cache, use a special branch specifically for merging with the cache // (we use < rather than <= since the block size might be one more than iterator.length()) @@ -434,11 +425,11 @@ namespace detail if (comp(proj(*std::prev(B1.end)), proj(*A1.start))) { // the two ranges are in reverse order, so move them in reverse order into the cache - detail::move(A1.start, A1.end, cache.begin() + B1.length()); - detail::move(B1.start, B1.end, cache.begin()); + detail::move(A1.start, A1.end, cache_begin + B1.length()); + detail::move(B1.start, B1.end, cache_begin); } else if (comp(proj(*B1.start), proj(*std::prev(A1.end)))) { // these two ranges weren't already in order, so merge them into the cache - merge_move(A1.start, A1.end, B1.start, B1.end, cache.begin(), + merge_move(A1.start, A1.end, B1.start, B1.end, cache_begin, compare, projection, projection); } else { // if A1, B1, A2, and B2 are all in order, skip doing anything else @@ -446,30 +437,30 @@ namespace detail not comp(proj(*A2.start), proj(*std::prev(B1.end)))) continue; // move A1 and B1 into the cache in the same order - detail::move(A1.start, B1.end, cache.begin()); + detail::move(A1.start, B1.end, cache_begin); } A1 = { A1.start, B1.end }; // merge A2 and B2 into the cache if (comp(proj(*std::prev(B2.end)), proj(*A2.start))) { // the two ranges are in reverse order, so move them in reverse order into the cache - detail::move(A2.start, A2.end, cache.begin() + (A1.length() + B2.length())); - detail::move(B2.start, B2.end, cache.begin() + A1.length()); + detail::move(A2.start, A2.end, cache_begin + (A1.length() + B2.length())); + detail::move(B2.start, B2.end, cache_begin + A1.length()); } else if (comp(proj(*B2.start), proj(*std::prev(A2.end)))) { // these two ranges weren't already in order, so merge them into the cache - merge_move(A2.start, A2.end, B2.start, B2.end, cache.begin() + A1.length(), + merge_move(A2.start, A2.end, B2.start, B2.end, cache_begin + A1.length(), compare, projection, projection); } else { // move A2 and B2 into the cache in the same order - detail::move(A2.start, B2.end, cache.begin() + A1.length()); + detail::move(A2.start, B2.end, cache_begin + A1.length()); } A2 = { A2.start, B2.end }; // merge A1 and A2 from the cache into the array - Range A3 = { cache.begin(), cache.begin() + A1.length() }; - Range B3 = { - cache.begin() + A1.length(), - cache.begin() + (A1.length() + A2.length()) + Range A3 = { cache_begin, cache_begin + A1.length() }; + Range B3 = { + cache_begin + A1.length(), + cache_begin + (A1.length() + A2.length()) }; if (comp(proj(*std::prev(B3.end)), proj(*A3.start))) { @@ -502,7 +493,7 @@ namespace detail } else if (comp(proj(*B.start), proj(*std::prev(A.end)))) { // these two ranges weren't already in order, so we need to merge them buffered_inplace_merge(A.start, A.end, B.end, compare, projection, - A.length(), B.length(), cache.begin()); + A.length(), B.length(), cache_begin); } } } @@ -763,7 +754,7 @@ namespace detail // if the first unevenly sized A block fits into the cache, move it there for when we go to Merge it // otherwise, if the second buffer is available, block swap the contents into that if (cache_size > 0 && lastA.length() <= cache_size) { - detail::move(lastA.start, lastA.end, cache.begin()); + detail::move(lastA.start, lastA.end, cache_begin); } else if (buffer2.length() > 0) { detail::swap_ranges_overlap(lastA.start, lastA.end, buffer2.start); } @@ -797,7 +788,7 @@ namespace detail // internal buffer exists we'll use it, otherwise we'll use a strictly // in-place merge algorithm if (cache_size > 0 && lastA.length() <= cache_size) { - half_inplace_merge(cache.begin(), cache.begin() + lastA.length(), + half_inplace_merge(cache_begin, cache_begin + lastA.length(), lastA.end, B_split, lastA.start, (std::min)(lastA.length(), B_split - lastA.end), compare, projection); @@ -812,7 +803,7 @@ namespace detail // move the previous A block into the cache or buffer2, since // that's where we need it to be when we go to merge it anyway if (block_size <= cache_size) { - detail::move(blockA.start, blockA.start + block_size, cache.begin()); + 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_overlap(blockA.start, blockA.start + block_size, buffer2.start); @@ -859,7 +850,7 @@ namespace detail // merge the last A block with the remaining B values if (cache_size > 0 && lastA.length() <= cache_size) { - half_inplace_merge(cache.begin(), cache.begin() + lastA.length(), + half_inplace_merge(cache_begin, cache_begin + lastA.length(), lastA.end, B.end, lastA.start, (std::min)(lastA.length(), B.end - lastA.end), compare, projection); @@ -927,8 +918,15 @@ namespace detail Compare compare, Projection projection) -> void { - Wiki::sort(std::move(first), std::move(last), - std::move(compare), std::move(projection)); + // use a small cache to speed up some of the operations + // just keep in mind that making it too small ruins the point (nothing will fit into it), + // and making it too large also ruins the point (so much for "low memory"!) + using rvalue_type = rvalue_type_t; + typename BufferProvider::template buffer cache(last - first); + + Wiki::sort(std::move(first), std::move(last), + cache.begin(), cache.size(), + std::move(compare), std::move(projection)); } }} From 0e3e9fa9b30de501ab9fba97daf37dbb3152d0d9 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 4 Oct 2021 15:58:02 +0200 Subject: [PATCH 26/79] Use a single PRNG for test distributions The test suite used to instantiate a thread_local PRNG for every instantiation of dist::shuffled::operator() and shuffled_16_values. This commit introduces a single thread_local PRNG common to all distrubtions, which makes the whole TLS segment of the test suite executable disappear. --- testsuite/main.cpp | 5 ++++- testsuite/testing-tools/distributions.h | 21 ++++++++------------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/testsuite/main.cpp b/testsuite/main.cpp index 934d1131..753c525f 100644 --- a/testsuite/main.cpp +++ b/testsuite/main.cpp @@ -1,6 +1,9 @@ /* - * Copyright (c) 2015-2018 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #define CATCH_CONFIG_MAIN #include +#include + +thread_local std::mt19937_64 dist::distributions_prng(Catch::rngSeed()); diff --git a/testsuite/testing-tools/distributions.h b/testsuite/testing-tools/distributions.h index 0c3c6d20..6979f682 100644 --- a/testsuite/testing-tools/distributions.h +++ b/testsuite/testing-tools/distributions.h @@ -9,15 +9,16 @@ // Headers //////////////////////////////////////////////////////////// #include -#include -#include #include #include -#include #include namespace dist { + // Pseudo-random number generator, used by some distributions + // Definition in main.cpp + extern thread_local std::mt19937_64 distributions_prng; + template struct distribution { @@ -40,9 +41,6 @@ namespace dist auto operator()(OutputIterator out, long long int size, T start=T(0)) const -> void { - // Pseudo-random number generator - thread_local std::mt19937 engine(Catch::rngSeed()); - std::vector vec; vec.reserve(size); @@ -50,8 +48,8 @@ namespace dist for (auto i = start ; i < end ; ++i) { vec.emplace_back(i); } - std::shuffle(std::begin(vec), std::end(vec), engine); - std::move(std::begin(vec), std::end(vec), out); + std::shuffle(vec.begin(), vec.end(), distributions_prng); + std::move(vec.begin(), vec.end(), out); } }; @@ -62,17 +60,14 @@ namespace dist auto operator()(OutputIterator out, long long int size) const -> void { - // Pseudo-random number generator - thread_local std::mt19937 engine(Catch::rngSeed()); - std::vector vec; vec.reserve(size); for (long long int i = 0 ; i < size ; ++i) { vec.emplace_back(i % 16); } - std::shuffle(std::begin(vec), std::end(vec), engine); - std::move(std::begin(vec), std::end(vec), out); + std::shuffle(vec.begin(), vec.end(), distributions_prng); + std::move(vec.begin(), vec.end(), out); } }; From 6ec0e86606b8628ef18fbd81771a755250ed8669 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 4 Oct 2021 18:16:29 +0200 Subject: [PATCH 27/79] Remove template parameter Branchless in pdqsort_loop Replace it with a constexpr variable inside the function: in cpp-sort this parameter is always fully dependent on the Compare and Projection parameters. --- include/cpp-sort/detail/pdqsort.h | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/include/cpp-sort/detail/pdqsort.h b/include/cpp-sort/detail/pdqsort.h index d2823e3a..aa8f3cf0 100644 --- a/include/cpp-sort/detail/pdqsort.h +++ b/include/cpp-sort/detail/pdqsort.h @@ -422,8 +422,7 @@ namespace detail } - template + template auto pdqsort_loop(RandomAccessIterator begin, RandomAccessIterator end, Compare compare, Projection projection, int bad_allowed, bool leftmost=true) @@ -431,6 +430,13 @@ namespace detail { using utility::iter_swap; using difference_type = difference_type_t; + using value_type = value_type_t; + using projected_type = projected_t; + + constexpr bool is_branchless = + utility::is_probably_branchless_comparison_v && + utility::is_probably_branchless_projection_v; + auto&& comp = utility::as_function(compare); auto&& proj = utility::as_function(projection); @@ -471,7 +477,7 @@ namespace detail } // Partition and get results. - std::pair part_result = Branchless ? + std::pair part_result = is_branchless ? partition_right_branchless(begin, end, compare, projection) : partition_right(begin, end, compare, projection); RandomAccessIterator pivot_pos = part_result.first; @@ -526,8 +532,7 @@ namespace detail // Sort the left partition first using recursion and do tail recursion elimination for // the right-hand partition. - pdqsort_loop( - begin, pivot_pos, compare, projection, bad_allowed, leftmost); + pdqsort_loop(begin, pivot_pos, compare, projection, bad_allowed, leftmost); begin = pivot_pos + 1; leftmost = false; } @@ -539,19 +544,12 @@ namespace detail Compare compare, Projection projection) -> void { - using value_type = value_type_t; - using projected_type = projected_t; - constexpr bool is_branchless = - utility::is_probably_branchless_comparison_v && - utility::is_probably_branchless_projection_v; - 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(size)); + pdqsort_detail::pdqsort_loop(std::move(begin), std::move(end), + std::move(compare), std::move(projection), + detail::log2(size)); } }} From ed3354dd1e64fffd164f13b064a1c3b268416835 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 5 Oct 2021 22:17:53 +0200 Subject: [PATCH 28/79] Fix links in documentation --- docs/Changelog.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/Changelog.md b/docs/Changelog.md index 5e936fda..5711453f 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -50,7 +50,7 @@ When compiled with C++17, **cpp-sort** might gain a few additional features depe This feature is available when the feature-testing macro `__cpp_nontype_template_parameter_auto` is defined. -* [[`sorter_facade`|Sorter facade]] range overloads can now be used in `constexpr` functions. +* [`sorter_facade`][sorter-facade] range overloads can now be used in `constexpr` functions. There is no specific feature macro available to test this, it starts working when `std::begin` and `std::end` are `constexpr`. @@ -94,11 +94,12 @@ When compiled with C++20, **cpp-sort** might gain a few additional features depe [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 + [sorter-facade]: https://github.com/Morwenn/cpp-sort/wiki/Sorter-facade [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) + [std-string-view]: https://en.cppreference.com/w/cpp/string/basic_string_view [utility-iter-move]: https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#iter_move-and-iter_swap From 9e93f08e64a666d97627126f4afb8346be35305e Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 5 Oct 2021 22:53:06 +0200 Subject: [PATCH 29/79] Conditionally make kLeonardoNumbers inline --- docs/Changelog.md | 5 +++++ include/cpp-sort/detail/config.h | 12 ++++++++++++ include/cpp-sort/detail/smoothsort.h | 7 ++++--- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/docs/Changelog.md b/docs/Changelog.md index 5711453f..6cf986ad 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -59,6 +59,11 @@ 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. +**Size improvements:** +* When used in different translation units, [`smooth_sorter`][smooth-sorter] might produce fewer duplicates and consume less binary size in C++17. + + This optimization is available when the feature-testing macro `__cpp_inline_variables` is available. + ## 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][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**. diff --git a/include/cpp-sort/detail/config.h b/include/cpp-sort/detail/config.h index 4fd795bc..25c8e5d4 100644 --- a/include/cpp-sort/detail/config.h +++ b/include/cpp-sort/detail/config.h @@ -16,6 +16,18 @@ # define __has_cpp_attribute(x) 0 #endif +//////////////////////////////////////////////////////////// +// Check for C++17 features + +// Make sure that there is a single variable before C++17, +// default to static in C++14 to avoid ODR issues + +#if defined(__cpp_inline_variables) +# define CPPSORT_INLINE_VARIABLE inline +#else +# define CPPSORT_INLINE_VARIABLE static +#endif + //////////////////////////////////////////////////////////// // Check for C++20 features diff --git a/include/cpp-sort/detail/smoothsort.h b/include/cpp-sort/detail/smoothsort.h index c047bb1b..5ca738d8 100644 --- a/include/cpp-sort/detail/smoothsort.h +++ b/include/cpp-sort/detail/smoothsort.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2020 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ @@ -28,6 +28,7 @@ #include #include #include +#include "config.h" namespace cppsort { @@ -37,12 +38,12 @@ namespace detail /* A constant containing the number of Leonardo numbers that can fit into * 64 bits. */ - static constexpr std::size_t kNumLeonardoNumbers = 92; + CPPSORT_INLINE_VARIABLE constexpr std::size_t kNumLeonardoNumbers = 92; /* A list of all the Leonardo numbers below 2^64, precomputed for * efficiency. */ - static constexpr std::uint_fast64_t kLeonardoNumbers[kNumLeonardoNumbers] = { + CPPSORT_INLINE_VARIABLE constexpr std::uint_fast64_t kLeonardoNumbers[kNumLeonardoNumbers] = { 1u, 1u, 3u, 5u, 9u, 15u, 25u, 41u, 67u, 109u, 177u, 287u, 465u, 753u, 1219u, 1973u, 3193u, 5167u, 8361u, 13529u, 21891u, 35421u, 57313u, 92735u, 150049u, 242785u, 392835u, 635621u, 1028457u, 1664079u, From b48fc6dc7c51075451855567789507879a1af718 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 10 Oct 2021 22:10:34 +0200 Subject: [PATCH 30/79] Slightly more consistent test names --- testsuite/heap_memory_exhaustion.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/testsuite/heap_memory_exhaustion.cpp b/testsuite/heap_memory_exhaustion.cpp index a2f4e29f..659f4681 100644 --- a/testsuite/heap_memory_exhaustion.cpp +++ b/testsuite/heap_memory_exhaustion.cpp @@ -19,7 +19,7 @@ // These tests shouldn't be part of the main test suite executable // -TEMPLATE_TEST_CASE( "test heap exhaustion for random-access sorters", "[sorters][heap_exhaustion]", +TEMPLATE_TEST_CASE( "heap exhaustion for random-access sorters", "[sorters][heap_exhaustion]", cppsort::grail_sorter<>, cppsort::heap_sorter, cppsort::insertion_sorter, @@ -46,7 +46,7 @@ TEMPLATE_TEST_CASE( "test heap exhaustion for random-access sorters", "[sorters] CHECK( std::is_sorted(std::begin(collection), std::end(collection)) ); } -TEMPLATE_TEST_CASE( "test heap exhaustion for bidirectional sorters", "[sorters][heap_exhaustion]", +TEMPLATE_TEST_CASE( "heap exhaustion for bidirectional sorters", "[sorters][heap_exhaustion]", cppsort::insertion_sorter, cppsort::merge_sorter, cppsort::quick_merge_sorter, @@ -65,7 +65,7 @@ TEMPLATE_TEST_CASE( "test heap exhaustion for bidirectional sorters", "[sorters] CHECK( std::is_sorted(std::begin(collection), std::end(collection)) ); } -TEMPLATE_TEST_CASE( "test heap exhaustion for forward sorters", "[sorters][heap_exhaustion]", +TEMPLATE_TEST_CASE( "heap exhaustion for forward sorters", "[sorters][heap_exhaustion]", cppsort::merge_sorter, cppsort::quick_merge_sorter, cppsort::quick_sorter, From ca13ac9c092a1f9723bed81a6a428ab83b707cb7 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 11 Oct 2021 17:52:39 +0200 Subject: [PATCH 31/79] Fully qualify some functions in tests --- testsuite/utility/sorting_networks.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/testsuite/utility/sorting_networks.cpp b/testsuite/utility/sorting_networks.cpp index 3dae5317..200f3c0a 100644 --- a/testsuite/utility/sorting_networks.cpp +++ b/testsuite/utility/sorting_networks.cpp @@ -31,13 +31,13 @@ TEST_CASE( "sorting with index pairs", "[utility][sorting_networks]" ) SECTION( "swap_index_pairs" ) { - swap_index_pairs(vec.begin(), pairs); + cppsort::utility::swap_index_pairs(vec.begin(), pairs); CHECK( std::is_sorted(vec.begin(), vec.end()) ); } SECTION( "swap_index_pairs_force_unroll" ) { - swap_index_pairs_force_unroll(vec.begin(), pairs); + cppsort::utility::swap_index_pairs_force_unroll(vec.begin(), pairs); CHECK( std::is_sorted(vec.begin(), vec.end()) ); } } @@ -53,13 +53,13 @@ TEST_CASE( "sorting with index pairs from sorting_network_sorter", SECTION( "swap_index_pairs" ) { - swap_index_pairs(vec.begin(), pairs); + cppsort::utility::swap_index_pairs(vec.begin(), pairs); CHECK( std::is_sorted(vec.begin(), vec.end()) ); } SECTION( "swap_index_pairs_force_unroll" ) { - swap_index_pairs_force_unroll(vec.begin(), pairs); + cppsort::utility::swap_index_pairs_force_unroll(vec.begin(), pairs); CHECK( std::is_sorted(vec.begin(), vec.end()) ); } } From 33e48d89d8d06b982a9654c0665d9b7a4128354e Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 11 Oct 2021 18:38:47 +0200 Subject: [PATCH 32/79] Reimplement dist::shuffled without extra memory Instead of using iota+shuffle on a temporary collection, this new method generated a shuffle of all integers in range [a, b] without the space overhead thanks to a linear congruential generator. --- include/cpp-sort/detail/bitops.h | 13 +++++++++++ testsuite/main.cpp | 1 + testsuite/testing-tools/distributions.h | 31 ++++++++++++++++++------- 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/include/cpp-sort/detail/bitops.h b/include/cpp-sort/detail/bitops.h index 56e61cd2..14c2714f 100644 --- a/include/cpp-sort/detail/bitops.h +++ b/include/cpp-sort/detail/bitops.h @@ -29,6 +29,19 @@ namespace detail return n & ~(n >> 1); } + // Returns 2^ceil(log2(n)), assumes n > 0 + template + constexpr auto hyperceil(Unsigned n) + -> Unsigned + { + constexpr auto bound = std::numeric_limits::digits / 2; + --n; + for (std::size_t i = 1 ; i <= bound ; i <<= 1) { + n |= (n >> i); + } + return ++n; + } + // Returns floor(log2(n)), assumes n > 0 #if defined(__GNUC__) || defined(__clang__) diff --git a/testsuite/main.cpp b/testsuite/main.cpp index 753c525f..11148aca 100644 --- a/testsuite/main.cpp +++ b/testsuite/main.cpp @@ -7,3 +7,4 @@ #include thread_local std::mt19937_64 dist::distributions_prng(Catch::rngSeed()); +thread_local std::uniform_int_distribution dist::randint; diff --git a/testsuite/testing-tools/distributions.h b/testsuite/testing-tools/distributions.h index 6979f682..9ee58ddf 100644 --- a/testsuite/testing-tools/distributions.h +++ b/testsuite/testing-tools/distributions.h @@ -9,6 +9,7 @@ // Headers //////////////////////////////////////////////////////////// #include +#include #include #include #include @@ -19,6 +20,8 @@ namespace dist // Definition in main.cpp extern thread_local std::mt19937_64 distributions_prng; + extern thread_local std::uniform_int_distribution randint; + template struct distribution { @@ -37,19 +40,29 @@ namespace dist struct shuffled: distribution { - template - auto operator()(OutputIterator out, long long int size, T start=T(0)) const + template + auto operator()(OutputIterator out, long long int size, long long int start=0ll) const -> void { - std::vector vec; - vec.reserve(size); + assert(size >= 4); + using param_t = typename std::uniform_int_distribution::param_type; + + // Generate a shuffle of all the integers in the range [start, start + size) + // with a linear congruential generator + // https://stackoverflow.com/a/44821946/1364752 - T end = start + size; - for (auto i = start ; i < end ; ++i) { - vec.emplace_back(i); + auto m = cppsort::detail::hyperceil(static_cast(size)); + auto a = randint(distributions_prng, param_t(1, (m >> 2) - 1)) * 4 + 1; + auto c = randint(distributions_prng, param_t(3, m)) | 1; + + auto x = 1ll; + for (auto i = 0ll; i < size; ++i) { + do { + x = (x * a + c) % m; + } while (x >= size); + out = static_cast(x + start); + ++out; } - std::shuffle(vec.begin(), vec.end(), distributions_prng); - std::move(vec.begin(), vec.end(), out); } }; From c4fad3646de11de14694b93d4c04bf6cf716ef37 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 13 Oct 2021 20:34:25 +0200 Subject: [PATCH 33/79] Bump codecov-action to v2 --- .github/workflows/code-coverage.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index 74d7571d..8c19bcb8 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -53,7 +53,7 @@ jobs: run: make gcov - name: Upload coverage info - uses: codecov/codecov-action@v1.2.1 + uses: codecov/codecov-action@v2 with: directory: ${{runner.workspace}}/build - functionalities: gcov + fail_ci_if_error: true From 953fbdb7c4b56f4d38f5bf1e5652745b4a4e3577 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 18 Oct 2021 14:15:16 +0200 Subject: [PATCH 34/79] Reimplement a uniform randint algorithm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The standard one has different implementations yielding different results in available standard libraries, which meant that results weren't reproducible anymore across standard libraries. Shipping our own means that it will produce the same results everywhere. The new algorithm was taken from Jérémie Lumbroso's *Optimal Discrete Uniform Generation from Coin Flips, and Applications*. --- include/cpp-sort/detail/random.h | 87 +++++++++++++++++++++++++ testsuite/main.cpp | 3 +- testsuite/testing-tools/distributions.h | 12 ++-- 3 files changed, 95 insertions(+), 7 deletions(-) create mode 100644 include/cpp-sort/detail/random.h diff --git a/include/cpp-sort/detail/random.h b/include/cpp-sort/detail/random.h new file mode 100644 index 00000000..baa0feb4 --- /dev/null +++ b/include/cpp-sort/detail/random.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2021 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_DETAIL_RANDOM_H_ +#define CPPSORT_DETAIL_RANDOM_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include + +namespace cppsort +{ +namespace detail +{ + //////////////////////////////////////////////////////////// + // rand_bit_generator + // + // Takes a standard URBG and allows to fetch random bits one + // by one for algorithms that use such random bits + + template + struct rand_bit_generator + { + public: + using result_type = typename URBG::result_type; + + explicit rand_bit_generator(URBG& engine): + engine_ptr(&engine), + word(0), + pos(0) + {} + + auto next_bit() + -> result_type + { + if (pos == 0) { + word = (*engine_ptr)(); + pos = URBG::word_size; + } + --pos; + return (word & (result_type(1) << pos)) >> pos; + } + + private: + URBG* engine_ptr; + typename URBG::result_type word; + int pos; + }; + + //////////////////////////////////////////////////////////// + // randint + // + // Returns a random integer in the range [low, high]. + // See *Optimal Discrete Uniform Generation from Coin Flips, + // and Applications* by Jrmie Lumbroso + + template + auto randint(Integer low, Integer high, URBG& generator) + -> Integer + { + using urng_int_type = typename URBG::result_type; + + if (low == high) { + return low; + } + urng_int_type n = high - low; + + urng_int_type v = 1; + urng_int_type c = 0; + while (true) { + v <<= 1; + c = (c << 1) + generator.next_bit(); + if (v >= n) { + if (c < n) { + return static_cast(low + c); + } else { + v -= n; + c -= n; + } + } + } + } +}} + +#endif // CPPSORT_DETAIL_RANDOM_H_ diff --git a/testsuite/main.cpp b/testsuite/main.cpp index 11148aca..aa189534 100644 --- a/testsuite/main.cpp +++ b/testsuite/main.cpp @@ -4,7 +4,8 @@ */ #define CATCH_CONFIG_MAIN #include +#include #include thread_local std::mt19937_64 dist::distributions_prng(Catch::rngSeed()); -thread_local std::uniform_int_distribution dist::randint; +thread_local cppsort::detail::rand_bit_generator dist::gen(dist::distributions_prng); diff --git a/testsuite/testing-tools/distributions.h b/testsuite/testing-tools/distributions.h index 9ee58ddf..d8adbbdb 100644 --- a/testsuite/testing-tools/distributions.h +++ b/testsuite/testing-tools/distributions.h @@ -13,14 +13,15 @@ #include #include #include +#include namespace dist { // Pseudo-random number generator, used by some distributions // Definition in main.cpp extern thread_local std::mt19937_64 distributions_prng; - - extern thread_local std::uniform_int_distribution randint; + // Class allowing to fetch random bits one by one + extern thread_local cppsort::detail::rand_bit_generator gen; template struct distribution @@ -45,15 +46,14 @@ namespace dist -> void { assert(size >= 4); - using param_t = typename std::uniform_int_distribution::param_type; // Generate a shuffle of all the integers in the range [start, start + size) // with a linear congruential generator // https://stackoverflow.com/a/44821946/1364752 - auto m = cppsort::detail::hyperceil(static_cast(size)); - auto a = randint(distributions_prng, param_t(1, (m >> 2) - 1)) * 4 + 1; - auto c = randint(distributions_prng, param_t(3, m)) | 1; + long long int m = cppsort::detail::hyperceil(size); + auto a = cppsort::detail::randint(1ll, (m >> 2) - 1, dist::gen) * 4 + 1; + auto c = cppsort::detail::randint(3ll, m, dist::gen) | 1; auto x = 1ll; for (auto i = 0ll; i < size; ++i) { From 35b6b343f8ef1b060add65fb8223bce892144626 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 21 Oct 2021 15:33:56 +0200 Subject: [PATCH 35/79] Fix segfault on OSX The segfault was introduced with the previous commit, and most likely linked to storing a pointer to a thread_local object and trying to access it from another thread. This commit shuffles things around to avoid that issue, but also to avoid random thread_local issues with MinGW-w64 which don't feel deserved unlike the OSX one. --- include/cpp-sort/detail/random.h | 53 ++++++++++++++++++++++--- testsuite/main.cpp | 5 ++- testsuite/testing-tools/distributions.h | 45 +++++---------------- 3 files changed, 62 insertions(+), 41 deletions(-) diff --git a/include/cpp-sort/detail/random.h b/include/cpp-sort/detail/random.h index baa0feb4..3fb2d04d 100644 --- a/include/cpp-sort/detail/random.h +++ b/include/cpp-sort/detail/random.h @@ -8,7 +8,12 @@ //////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// +#include #include +#include +#include +#include +#include "bitops.h" namespace cppsort { @@ -26,8 +31,8 @@ namespace detail public: using result_type = typename URBG::result_type; - explicit rand_bit_generator(URBG& engine): - engine_ptr(&engine), + explicit rand_bit_generator(URBG&& engine): + engine(std::move(engine)), word(0), pos(0) {} @@ -36,7 +41,7 @@ namespace detail -> result_type { if (pos == 0) { - word = (*engine_ptr)(); + word = engine(); pos = URBG::word_size; } --pos; @@ -44,8 +49,8 @@ namespace detail } private: - URBG* engine_ptr; - typename URBG::result_type word; + URBG engine; + result_type word; int pos; }; @@ -82,6 +87,44 @@ namespace detail } } } + + //////////////////////////////////////////////////////////// + // fill_with_shuffle + // + // Takes an output iterator and fills the corresponding + // range with a shuffle of all the integers in the range + // [start, start + size] + + template< + typename T=long long int, + typename OutputIterator, + typename URBG, + typename Projection = utility::identity + > + auto fill_with_shuffle(OutputIterator out, long long int size, long long int start, + URBG& engine, Projection projection={}) + -> void + { + assert(size >= 4); + auto&& proj = utility::as_function(projection); + + // Generate a shuffle of all the integers in the range [start, start + size) + // with a linear congruential generator + // https://stackoverflow.com/a/44821946/1364752 + + long long int m = hyperceil(size); + auto a = randint(1ll, (m >> 2) - 1, engine) * 4 + 1; + auto c = randint(3ll, m, engine) | 1; + + auto x = 1ll; + for (auto i = 0ll; i < size; ++i) { + do { + x = (x * a + c) % m; + } while (x >= size); + *out = static_cast(proj(x + start)); + ++out; + } + } }} #endif // CPPSORT_DETAIL_RANDOM_H_ diff --git a/testsuite/main.cpp b/testsuite/main.cpp index aa189534..85a20619 100644 --- a/testsuite/main.cpp +++ b/testsuite/main.cpp @@ -7,5 +7,6 @@ #include #include -thread_local std::mt19937_64 dist::distributions_prng(Catch::rngSeed()); -thread_local cppsort::detail::rand_bit_generator dist::gen(dist::distributions_prng); +thread_local cppsort::detail::rand_bit_generator dist::gen( + std::mt19937_64(Catch::rngSeed()) +); diff --git a/testsuite/testing-tools/distributions.h b/testsuite/testing-tools/distributions.h index d8adbbdb..849f8d52 100644 --- a/testsuite/testing-tools/distributions.h +++ b/testsuite/testing-tools/distributions.h @@ -8,19 +8,14 @@ //////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// -#include -#include #include -#include #include #include namespace dist { - // Pseudo-random number generator, used by some distributions - // Definition in main.cpp - extern thread_local std::mt19937_64 distributions_prng; - // Class allowing to fetch random bits one by one + // Utility allowing to fetch random bits from a URBG one by one, + // defined in main.cpp extern thread_local cppsort::detail::rand_bit_generator gen; template @@ -45,42 +40,24 @@ namespace dist auto operator()(OutputIterator out, long long int size, long long int start=0ll) const -> void { - assert(size >= 4); - - // Generate a shuffle of all the integers in the range [start, start + size) - // with a linear congruential generator - // https://stackoverflow.com/a/44821946/1364752 - - long long int m = cppsort::detail::hyperceil(size); - auto a = cppsort::detail::randint(1ll, (m >> 2) - 1, dist::gen) * 4 + 1; - auto c = cppsort::detail::randint(3ll, m, dist::gen) | 1; - - auto x = 1ll; - for (auto i = 0ll; i < size; ++i) { - do { - x = (x * a + c) % m; - } while (x >= size); - out = static_cast(x + start); - ++out; - } + cppsort::detail::fill_with_shuffle(out, size, start, gen); } }; struct shuffled_16_values: distribution { - template + static constexpr auto mod_16(long long int value) + -> long long int + { + return value % 16; + } + + template auto operator()(OutputIterator out, long long int size) const -> void { - std::vector vec; - vec.reserve(size); - - for (long long int i = 0 ; i < size ; ++i) { - vec.emplace_back(i % 16); - } - std::shuffle(vec.begin(), vec.end(), distributions_prng); - std::move(vec.begin(), vec.end(), out); + cppsort::detail::fill_with_shuffle(out, size, 0, gen, &mod_16); } }; From 34127b3e3f9938d17a3172d8d51e80c96c31fe20 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 21 Oct 2021 15:57:24 +0200 Subject: [PATCH 36/79] Fix most verxing parse issue --- testsuite/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testsuite/main.cpp b/testsuite/main.cpp index 85a20619..deb5aa2f 100644 --- a/testsuite/main.cpp +++ b/testsuite/main.cpp @@ -7,6 +7,6 @@ #include #include -thread_local cppsort::detail::rand_bit_generator dist::gen( +thread_local cppsort::detail::rand_bit_generator dist::gen{ std::mt19937_64(Catch::rngSeed()) -); +}; From b0ac6a03768673a01948843a662deb0b8c966226 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 21 Oct 2021 17:29:29 +0200 Subject: [PATCH 37/79] Update CMake-codecov scripts from upstream --- cmake/FindGcov.cmake | 17 +++++++++++------ cmake/FindLcov.cmake | 21 +++++++++++++++------ cmake/Findcodecov.cmake | 25 +++++++++++++++++++------ cmake/llvm-cov-wrapper | 4 ++-- 4 files changed, 47 insertions(+), 20 deletions(-) diff --git a/cmake/FindGcov.cmake b/cmake/FindGcov.cmake index 6ffd6eac..db03e534 100644 --- a/cmake/FindGcov.cmake +++ b/cmake/FindGcov.cmake @@ -1,7 +1,7 @@ # This file is part of CMake-codecov. # # Copyright (c) -# 2015-2017 RWTH Aachen University, Federal Republic of Germany +# 2015-2020 RWTH Aachen University, Federal Republic of Germany # # See the LICENSE file in the package base directory for details # @@ -19,7 +19,7 @@ set(CMAKE_REQUIRED_QUIET ${codecov_FIND_QUIETLY}) get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) foreach (LANG ${ENABLED_LANGUAGES}) - # Gcov evaluation is dependend on the used compiler. Check gcov support for + # Gcov evaluation is dependent on the used compiler. Check gcov support for # each compiler that is used. If gcov binary was already found for this # compiler, do not try to find it again. if (NOT GCOV_${CMAKE_${LANG}_COMPILER_ID}_BIN) @@ -35,7 +35,7 @@ foreach (LANG ${ENABLED_LANGUAGES}) find_program(GCOV_BIN NAMES gcov-${GCC_VERSION} gcov HINTS ${COMPILER_PATH}) - elseif ("${CMAKE_${LANG}_COMPILER_ID}" STREQUAL "Clang") + elseif ("${CMAKE_${LANG}_COMPILER_ID}" MATCHES "^(Apple)?Clang$") # Some distributions like Debian ship llvm-cov with the compiler # version appended as llvm-cov-x.y. To find this binary we'll build # the suggested binary name with the compiler version. @@ -105,7 +105,8 @@ endif (NOT TARGET gcov) # Gcov on any source file of once and store the gcov file in the same # directory. function (add_gcov_target TNAME) - set(TDIR ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${TNAME}.dir) + get_target_property(TBIN_DIR ${TNAME} BINARY_DIR) + set(TDIR ${TBIN_DIR}/CMakeFiles/${TNAME}.dir) # We don't have to check, if the target has support for coverage, thus this # will be checked by add_coverage_target in Findcoverage.cmake. Instead we @@ -135,14 +136,18 @@ function (add_gcov_target TNAME) set(BUFFER "") + set(NULL_DEVICE "/dev/null") + if(WIN32) + set(NULL_DEVICE "NUL") + endif() foreach(FILE ${SOURCES}) get_filename_component(FILE_PATH "${TDIR}/${FILE}" PATH) # call gcov add_custom_command(OUTPUT ${TDIR}/${FILE}.gcov - COMMAND ${GCOV_ENV} ${GCOV_BIN} ${TDIR}/${FILE}.gcno > /dev/null + COMMAND ${GCOV_ENV} ${GCOV_BIN} -p ${TDIR}/${FILE}.gcno > ${NULL_DEVICE} DEPENDS ${TNAME} ${TDIR}/${FILE}.gcno - WORKING_DIRECTORY ${FILE_PATH} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) list(APPEND BUFFER ${TDIR}/${FILE}.gcov) diff --git a/cmake/FindLcov.cmake b/cmake/FindLcov.cmake index beb925ae..244d1c20 100644 --- a/cmake/FindLcov.cmake +++ b/cmake/FindLcov.cmake @@ -1,7 +1,7 @@ # This file is part of CMake-codecov. # # Copyright (c) -# 2015-2017 RWTH Aachen University, Federal Republic of Germany +# 2015-2020 RWTH Aachen University, Federal Republic of Germany # # See the LICENSE file in the package base directory for details # @@ -53,7 +53,7 @@ include(FindPackageHandleStandardArgs) find_program(LCOV_BIN lcov) find_program(GENINFO_BIN geninfo) find_program(GENHTML_BIN genhtml) -find_package_handle_standard_args(lcov +find_package_handle_standard_args(Lcov REQUIRED_VARS LCOV_BIN GENINFO_BIN GENHTML_BIN ) @@ -159,7 +159,9 @@ function (lcov_capture_initial_tgt TNAME) set(GCOV_ENV "${GCOV_${TCOMPILER}_ENV}") - set(TDIR ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${TNAME}.dir) + get_target_property(TBIN_DIR ${TNAME} BINARY_DIR) + set(TDIR ${TBIN_DIR}/CMakeFiles/${TNAME}.dir) + set(GENINFO_FILES "") foreach(FILE ${SOURCES}) # generate empty coverage files @@ -249,8 +251,9 @@ function (lcov_capture_tgt TNAME) set(GCOV_BIN "${GCOV_${TCOMPILER}_BIN}") set(GCOV_ENV "${GCOV_${TCOMPILER}_ENV}") + get_target_property(TBIN_DIR ${TNAME} BINARY_DIR) + set(TDIR ${TBIN_DIR}/CMakeFiles/${TNAME}.dir) - set(TDIR ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${TNAME}.dir) set(GENINFO_FILES "") foreach(FILE ${SOURCES}) # Generate coverage files. If no .gcda file was generated during @@ -258,14 +261,20 @@ function (lcov_capture_tgt TNAME) set(OUTFILE "${TDIR}/${FILE}.info") list(APPEND GENINFO_FILES ${OUTFILE}) + # Create an empty .gcda file, so the target capture file can have a dependency on it. + # The capture file will only use this .gcda if it has a non-zero size (test -s). + add_custom_command(OUTPUT "${TDIR}/${FILE}.gcda" + COMMAND "${CMAKE_COMMAND}" -E touch "${TDIR}/${FILE}.gcda" + ) + add_custom_command(OUTPUT ${OUTFILE} - COMMAND test -f "${TDIR}/${FILE}.gcda" + COMMAND test -s "${TDIR}/${FILE}.gcda" && ${GCOV_ENV} ${GENINFO_BIN} --quiet --base-directory ${PROJECT_SOURCE_DIR} --gcov-tool ${GCOV_BIN} --output-filename ${OUTFILE} ${GENINFO_EXTERN_FLAG} ${TDIR}/${FILE}.gcda || cp ${OUTFILE}.init ${OUTFILE} - DEPENDS ${TNAME} ${TNAME}-capture-init + DEPENDS ${TNAME} ${TNAME}-capture-init "${TDIR}/${FILE}.gcda" COMMENT "Capturing coverage data for ${FILE}" ) endforeach() diff --git a/cmake/Findcodecov.cmake b/cmake/Findcodecov.cmake index fa135fa8..c20cf088 100644 --- a/cmake/Findcodecov.cmake +++ b/cmake/Findcodecov.cmake @@ -1,7 +1,7 @@ # This file is part of CMake-codecov. # # Copyright (c) -# 2015-2017 RWTH Aachen University, Federal Republic of Germany +# 2015-2020 RWTH Aachen University, Federal Republic of Germany # # See the LICENSE file in the package base directory for details # @@ -68,13 +68,13 @@ endif () -# Find the reuired flags foreach language. +# Find the required flags foreach language. set(CMAKE_REQUIRED_QUIET_SAVE ${CMAKE_REQUIRED_QUIET}) set(CMAKE_REQUIRED_QUIET ${codecov_FIND_QUIETLY}) get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) foreach (LANG ${ENABLED_LANGUAGES}) - # Coverage flags are not dependend on language, but the used compiler. So + # Coverage flags are not dependent on language, but the used compiler. So # instead of searching flags foreach language, search flags foreach compiler # used. set(COMPILER ${CMAKE_${LANG}_COMPILER_ID}) @@ -133,7 +133,15 @@ set(CMAKE_REQUIRED_QUIET ${CMAKE_REQUIRED_QUIET_SAVE}) # Helper function to get the language of a source file. function (codecov_lang_of_source FILE RETURN_VAR) - get_filename_component(FILE_EXT "${FILE}" EXT) + # Usually, only the last extension of the file should be checked, to avoid + # template files (i.e. *.t.cpp) are checked with the full file extension. + # However, this feature requires CMake 3.14 or later. + set(EXT_COMP "LAST_EXT") + if(${CMAKE_VERSION} VERSION_LESS "3.14.0") + set(EXT_COMP "EXT") + endif() + + get_filename_component(FILE_EXT "${FILE}" ${EXT_COMP}) string(TOLOWER "${FILE_EXT}" FILE_EXT) string(SUBSTRING "${FILE_EXT}" 1 -1 FILE_EXT) @@ -241,8 +249,13 @@ function(add_coverage_target TNAME) list(APPEND CLEAN_FILES "CMakeFiles/${TNAME}.dir/${FILE}") endforeach() - set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES - "${CLEAN_FILES}") + if(${CMAKE_VERSION} VERSION_LESS "3.15.0") + set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES + "${CLEAN_FILES}") + else() + set_directory_properties(PROPERTIES ADDITIONAL_CLEAN_FILES + "${CLEAN_FILES}") + endif() add_gcov_target(${TNAME}) diff --git a/cmake/llvm-cov-wrapper b/cmake/llvm-cov-wrapper index 2ac33102..f1cca5ef 100644 --- a/cmake/llvm-cov-wrapper +++ b/cmake/llvm-cov-wrapper @@ -3,7 +3,7 @@ # This file is part of CMake-codecov. # # Copyright (c) -# 2015-2017 RWTH Aachen University, Federal Republic of Germany +# 2015-2020 RWTH Aachen University, Federal Republic of Germany # # See the LICENSE file in the package base directory for details # @@ -19,7 +19,7 @@ fi # Get LLVM version to find out. LLVM_VERSION=$($LLVM_COV_BIN -version | grep -i "LLVM version" \ - | sed "s/^\([A-Za-z ]*\)\([0-9]\).\([0-9]\).*$/\2.\3/g") + | sed "s/^\([A-Za-z ]*\)\([0-9]*\).\([0-9]*\).*$/\2.\3/g") if [ "$1" = "-v" ] then From 8ed10f389a3d5729aec91fe08624675e3024de90 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 23 Oct 2021 22:33:41 +0200 Subject: [PATCH 38/79] Simplify coverage configuration in CMake --- testsuite/CMakeLists.txt | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/testsuite/CMakeLists.txt b/testsuite/CMakeLists.txt index a82a5a80..faa17843 100644 --- a/testsuite/CMakeLists.txt +++ b/testsuite/CMakeLists.txt @@ -14,11 +14,6 @@ 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 or download Catch2 @@ -39,6 +34,15 @@ else() endif() include(Catch) +######################################## +# Configure coverage + +if (CPPSORT_ENABLE_COVERAGE) + set(ENABLE_COVERAGE ON CACHE BOOL "Enable coverage build." FORCE) + find_package(codecov) + list(APPEND LCOV_REMOVE_PATTERNS "'/usr/*'") +endif() + ######################################## # Configure runtime tests @@ -91,12 +95,7 @@ macro(configure_tests target) endif() if (CPPSORT_ENABLE_COVERAGE) - find_package(codecov) add_coverage(${target}) - - # Set flags specific to coverage builds - target_compile_options(${target} PRIVATE --coverage) - set_property(TARGET ${target} APPEND_STRING PROPERTY LINK_FLAGS " --coverage") endif() endmacro() @@ -238,14 +237,6 @@ if (NOT "${CPPSORT_SANITIZE}" MATCHES "address|memory") configure_tests(heap-memory-exhaustion-tests) endif() -######################################## -# Configure coverage - -if (CPPSORT_ENABLE_COVERAGE) - list(APPEND LCOV_REMOVE_PATTERNS "'/usr/*'") - coverage_evaluate() -endif() - ######################################## # Configure Valgrind From 112d08c48121eb3c296d213e12c5d2c0563f3e77 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 24 Oct 2021 00:06:31 +0200 Subject: [PATCH 39/79] Simplify/fix coverage action --- .github/workflows/code-coverage.yml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index 8c19bcb8..b658f145 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -17,9 +17,6 @@ on: - 'include/**' - 'testsuite/**' -env: - BUILD_TYPE: Debug - jobs: upload-coverage: runs-on: ubuntu-latest @@ -33,19 +30,19 @@ jobs: working-directory: ${{runner.workspace}} run: > cmake -H${{github.event.repository.name}} -Bbuild - -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" - -DCPPSORT_ENABLE_COVERAGE=true + -DCMAKE_BUILD_TYPE=Debug + -DCPPSORT_ENABLE_COVERAGE=ON -G"Unix Makefiles" - name: Build with coverage shell: bash working-directory: ${{runner.workspace}}/build - run: cmake --build . --config $BUILD_TYPE -j 2 + run: cmake --build . --config Debug -j 2 - name: Run the test suite shell: bash working-directory: ${{runner.workspace}}/build - run: ctest -C Release --output-on-failure + run: ctest -C Debug --output-on-failure - name: Create coverage info shell: bash From e52f423a57aa5129ae42c5a32db8ed4d5549a045 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 24 Oct 2021 22:56:53 +0200 Subject: [PATCH 40/79] More coverage tweaks --- .github/workflows/code-coverage.yml | 4 ++-- cmake/Findcodecov.cmake | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index b658f145..65943ee7 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -47,10 +47,10 @@ jobs: - name: Create coverage info shell: bash working-directory: ${{runner.workspace}}/build - run: make gcov + run: cmake --build . --target gcov - name: Upload coverage info uses: codecov/codecov-action@v2 with: - directory: ${{runner.workspace}}/build + directory: ${{runner.workspace}}/build/testsuite fail_ci_if_error: true diff --git a/cmake/Findcodecov.cmake b/cmake/Findcodecov.cmake index c20cf088..89f9198d 100644 --- a/cmake/Findcodecov.cmake +++ b/cmake/Findcodecov.cmake @@ -74,6 +74,11 @@ set(CMAKE_REQUIRED_QUIET ${codecov_FIND_QUIETLY}) get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) foreach (LANG ${ENABLED_LANGUAGES}) + if (NOT ${LANG} MATCHES "^(C|CXX|Fortran)$") + message(STATUS "Skipping coverage for unsupported language: ${LANG}") + continue() + endif () + # Coverage flags are not dependent on language, but the used compiler. So # instead of searching flags foreach language, search flags foreach compiler # used. From 509bad41592a32884a2f96924d4aa4c95dac19e7 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 24 Oct 2021 23:49:38 +0200 Subject: [PATCH 41/79] Move codecov.yml to .github codecov recognizes the file in .github, which allows to avoid cluttering the root of the project with CI-specific files. --- codecov.yml => .github/.codecov.yml | 0 .github/workflows/code-coverage.yml | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename codecov.yml => .github/.codecov.yml (100%) diff --git a/codecov.yml b/.github/.codecov.yml similarity index 100% rename from codecov.yml rename to .github/.codecov.yml diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index 65943ee7..684e304f 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -11,9 +11,9 @@ on: - 2.0.0-develop paths: - '.github/workflows/code-coverage.yml' + - '.github/.codecov.yml' - 'CMakeLists.txt' - 'cmake/**' - - 'codecov.yml' - 'include/**' - 'testsuite/**' From 8cb6ec2a1bee40c9449366c2fb7c8e6c53c717d9 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 26 Oct 2021 13:32:42 +0200 Subject: [PATCH 42/79] Use LCOV for coverage --- .github/workflows/code-coverage.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index 684e304f..e9a1d380 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -25,6 +25,9 @@ jobs: - name: Checkout project uses: actions/checkout@v2 + - name: Install LCOV + run: sudo apt-get install -y lcov + - name: Configure CMake shell: bash working-directory: ${{runner.workspace}} @@ -44,13 +47,14 @@ jobs: working-directory: ${{runner.workspace}}/build run: ctest -C Debug --output-on-failure - - name: Create coverage info + - name: Capture coverage info shell: bash working-directory: ${{runner.workspace}}/build - run: cmake --build . --target gcov + run: cmake --build . --target lcov-capture - name: Upload coverage info uses: codecov/codecov-action@v2 with: - directory: ${{runner.workspace}}/build/testsuite + directory: ${{runner.workspace}}/build/lcov/data + files: '*.info' fail_ci_if_error: true From 9e309f03af55b450f8f9e4e6f4a259438f752dca Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 30 Oct 2021 12:44:41 +0200 Subject: [PATCH 43/79] Test MOPs with collections of 0~or 1 elements --- testsuite/probes/every_probe_common.cpp | 34 +++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/testsuite/probes/every_probe_common.cpp b/testsuite/probes/every_probe_common.cpp index 3aacd5b7..33d55fac 100644 --- a/testsuite/probes/every_probe_common.cpp +++ b/testsuite/probes/every_probe_common.cpp @@ -58,3 +58,37 @@ TEMPLATE_TEST_CASE( "test every probe with a sorted collection", "[probe]", auto presortedness = mop(collection); CHECK( presortedness == 0 ); } + +TEMPLATE_TEST_CASE( "test every probe with a 0 or 1 element", "[probe]", + decltype(cppsort::probe::block), + decltype(cppsort::probe::dis), + decltype(cppsort::probe::enc), + decltype(cppsort::probe::exc), + decltype(cppsort::probe::ham), + decltype(cppsort::probe::inv), + decltype(cppsort::probe::max), + decltype(cppsort::probe::mono), + decltype(cppsort::probe::osc), + decltype(cppsort::probe::rem), + decltype(cppsort::probe::runs), + decltype(cppsort::probe::sus) ) +{ + // Ensure that all measures of presortedness return 0 when + // given a collection with 0 or 1 element + + std::decay_t mop; + + SECTION( "empty collection" ) + { + std::vector collection; + auto presortedness = mop(collection); + CHECK( presortedness == 0 ); + } + + SECTION( "one-element collection" ) + { + std::vector collection = { 42 }; + auto presortedness = mop(collection); + CHECK( presortedness == 0 ); + } +} From 702ec5f30b72ac0e6c286775b45545be95ed6336 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 31 Oct 2021 12:51:53 +0100 Subject: [PATCH 44/79] Fix unstable vergesort Due to what I suspect to be leftover tests from the previous vergesort redesign, the unstable versions of vergesort were never run, and the stable algorithms were systematically called instead. --- include/cpp-sort/detail/vergesort.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/cpp-sort/detail/vergesort.h b/include/cpp-sort/detail/vergesort.h index e18b257f..f3c3818b 100644 --- a/include/cpp-sort/detail/vergesort.h +++ b/include/cpp-sort/detail/vergesort.h @@ -531,10 +531,10 @@ namespace verge { // 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))); + 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))); } constexpr auto default_sorter_for_impl(std::bidirectional_iterator_tag) From c1c59395ad993c8c54a74db4784a32598887487f Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 31 Oct 2021 14:01:18 +0100 Subject: [PATCH 45/79] Fix stable_adapter construction --- include/cpp-sort/sorters/default_sorter.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/include/cpp-sort/sorters/default_sorter.h b/include/cpp-sort/sorters/default_sorter.h index 3f675928..23aad1a6 100644 --- a/include/cpp-sort/sorters/default_sorter.h +++ b/include/cpp-sort/sorters/default_sorter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2020 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_SORTERS_DEFAULT_SORTER_H_ @@ -31,6 +31,12 @@ namespace cppsort struct stable_adapter: merge_sorter { + stable_adapter() = default; + + constexpr explicit stable_adapter(const default_sorter&) noexcept: + stable_adapter() + {} + using type = merge_sorter; }; From a5638ce0b313693ee4ced0a8b1734f57c84041ce Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 31 Oct 2021 17:45:16 +0100 Subject: [PATCH 46/79] Tweak stable_adapter construction - Optimize construction of stable_adapter by not always copying the passed sorter - Fix stable_adapter> construction from a stable_adapter --- include/cpp-sort/adapters/stable_adapter.h | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/include/cpp-sort/adapters/stable_adapter.h b/include/cpp-sort/adapters/stable_adapter.h index 5d329a46..88d4d5d5 100644 --- a/include/cpp-sort/adapters/stable_adapter.h +++ b/include/cpp-sort/adapters/stable_adapter.h @@ -219,7 +219,11 @@ namespace cppsort { stable_adapter() = default; - constexpr explicit stable_adapter(Sorter sorter): + constexpr explicit stable_adapter(const Sorter& sorter): + utility::adapter_storage(sorter) + {} + + constexpr explicit stable_adapter(Sorter&& sorter): utility::adapter_storage(std::move(sorter)) {} @@ -256,6 +260,16 @@ namespace cppsort struct stable_adapter>: stable_adapter { + stable_adapter() = default; + + constexpr explicit stable_adapter(const stable_adapter& sorter): + stable_adapter(sorter) + {} + + constexpr explicit stable_adapter(stable_adapter&& sorter): + stable_adapter(std::move(sorter)) + {} + using type = stable_adapter; }; From fef948dfbdc2e38c7674b3cea5b6f4af4142a55c Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 31 Oct 2021 18:48:57 +0100 Subject: [PATCH 47/79] Test stable_adapter with every sorter --- testsuite/adapters/mixed_adapters.cpp | 22 +---------- .../adapters/verge_adapter_every_sorter.cpp | 39 ++++++++++++++++++- 2 files changed, 39 insertions(+), 22 deletions(-) diff --git a/testsuite/adapters/mixed_adapters.cpp b/testsuite/adapters/mixed_adapters.cpp index c84e097f..a0a15135 100644 --- a/testsuite/adapters/mixed_adapters.cpp +++ b/testsuite/adapters/mixed_adapters.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2020 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include @@ -204,22 +203,3 @@ 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()) ); -} diff --git a/testsuite/adapters/verge_adapter_every_sorter.cpp b/testsuite/adapters/verge_adapter_every_sorter.cpp index 978f0e74..ec3f59d7 100644 --- a/testsuite/adapters/verge_adapter_every_sorter.cpp +++ b/testsuite/adapters/verge_adapter_every_sorter.cpp @@ -6,10 +6,13 @@ #include #include #include +#include #include #include #include +#include #include +#include TEMPLATE_TEST_CASE( "every sorter with verge_adapter", "[verge_adapter]", cppsort::cartesian_tree_sorter, @@ -42,5 +45,39 @@ TEMPLATE_TEST_CASE( "every sorter with verge_adapter", "[verge_adapter]", cppsort::verge_adapter sorter; sorter(collection); - CHECK( std::is_sorted(std::begin(collection), std::end(collection)) ); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); +} + +TEMPLATE_TEST_CASE( "every sorter with stable verge_adapter", "[verge_adapter][stable_adapter]", + cppsort::cartesian_tree_sorter, + cppsort::default_sorter, + cppsort::drop_merge_sorter, + cppsort::grail_sorter<>, + cppsort::heap_sorter, + cppsort::insertion_sorter, + cppsort::mel_sorter, + cppsort::merge_insertion_sorter, + cppsort::merge_sorter, + cppsort::pdq_sorter, + cppsort::poplar_sorter, + cppsort::quick_merge_sorter, + cppsort::quick_sorter, + cppsort::selection_sorter, + cppsort::slab_sorter, + cppsort::smooth_sorter, + cppsort::spin_sorter, + cppsort::split_sorter, + cppsort::std_sorter, + cppsort::tim_sorter, + cppsort::wiki_sorter> ) +{ + using wrapper = generic_stable_wrapper; + std::vector collection; collection.reserve(400); + auto distribution = dist::descending_plateau{}; + distribution(std::back_inserter(collection), 400); + helpers::iota(collection.begin(), collection.end(), 0, &wrapper::order); + + cppsort::stable_adapter> sorter; + sorter(collection, &wrapper::value); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); } From 105bdab8ff3853acbf66b32255e959ddce7b1e94 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 1 Nov 2021 00:08:22 +0100 Subject: [PATCH 48/79] Change stable_adapter::type to the class itself It as causing some weird issue where merge_sorter (the base class) was apparently not constructible from stable_adapter, yet we want sorter_adater::type to be constructible from T, so the target of ::type had to be changed. It's probably a minor breaking change but it doesn't matter much since the new one is actually correct, and default_sorter is deprecated anyway. --- include/cpp-sort/sorters/default_sorter.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/cpp-sort/sorters/default_sorter.h b/include/cpp-sort/sorters/default_sorter.h index 23aad1a6..592ecc39 100644 --- a/include/cpp-sort/sorters/default_sorter.h +++ b/include/cpp-sort/sorters/default_sorter.h @@ -37,7 +37,7 @@ namespace cppsort stable_adapter() {} - using type = merge_sorter; + using type = stable_adapter; }; //////////////////////////////////////////////////////////// From 6f0c4e5f2c032a2964b79cf098e49867e7b038bf Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 1 Nov 2021 01:14:05 +0100 Subject: [PATCH 49/79] Document expectation about stable_adapter::type --- docs/Sorter-adapters.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Sorter-adapters.md b/docs/Sorter-adapters.md index 33aee2a8..a317ab40 100644 --- a/docs/Sorter-adapters.md +++ b/docs/Sorter-adapters.md @@ -229,7 +229,7 @@ This adapter takes a sorter and alters its behavior (if needed) to produce a sta 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. +`stable_adapter` and its specializations might expose a `type` member type which aliases the *adapted 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. This `::type` must be constructible from an instance of the *adapted sorter* type. The *resulting sorter* is always stable. From a467e47eec6862dcf739023e477707d72eab9f1b Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 1 Nov 2021 01:16:03 +0100 Subject: [PATCH 50/79] Make vergesort use stable_t instead of stable_adapter --- include/cpp-sort/detail/vergesort.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/cpp-sort/detail/vergesort.h b/include/cpp-sort/detail/vergesort.h index f3c3818b..ff7004bc 100644 --- a/include/cpp-sort/detail/vergesort.h +++ b/include/cpp-sort/detail/vergesort.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2020 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_VERGESORT_H_ @@ -505,9 +505,9 @@ namespace verge template auto get_maybe_stable(std::true_type, Sorter&& sorter) - -> cppsort::stable_adapter + -> cppsort::stable_t { - return cppsort::stable_adapter(std::move(sorter)); + return cppsort::stable_t(std::move(sorter)); } template From a163877bac24f27af61a26d11118ac51be375385 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 1 Nov 2021 13:17:42 +0100 Subject: [PATCH 51/79] Don't gratuitously inhibit automatic moves When 'param' is a by-value function parameter, 'return param;' automatically moves, but not something like 'return param += 1;'. This commit avoids the inhibition of the automatic move by only using the name of the parameter in the return expression. --- include/cpp-sort/detail/associate_iterator.h | 9 ++++++--- include/cpp-sort/detail/merge_insertion_sort.h | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/include/cpp-sort/detail/associate_iterator.h b/include/cpp-sort/detail/associate_iterator.h index 23878564..3fde94f1 100644 --- a/include/cpp-sort/detail/associate_iterator.h +++ b/include/cpp-sort/detail/associate_iterator.h @@ -325,21 +325,24 @@ namespace detail friend auto operator+(associate_iterator it, difference_type size) -> associate_iterator { - return it += size; + it += size; + return it; } CPPSORT_ATTRIBUTE_NODISCARD friend auto operator+(difference_type size, associate_iterator it) -> associate_iterator { - return it += size; + it += size; + return it; } CPPSORT_ATTRIBUTE_NODISCARD friend auto operator-(associate_iterator it, difference_type size) -> associate_iterator { - return it -= size; + it -= size; + return it; } CPPSORT_ATTRIBUTE_NODISCARD diff --git a/include/cpp-sort/detail/merge_insertion_sort.h b/include/cpp-sort/detail/merge_insertion_sort.h index 14aa1a36..fb0023cf 100644 --- a/include/cpp-sort/detail/merge_insertion_sort.h +++ b/include/cpp-sort/detail/merge_insertion_sort.h @@ -212,21 +212,24 @@ namespace detail friend auto operator+(group_iterator it, difference_type size) -> group_iterator { - return it += size; + it += size; + return it; } CPPSORT_ATTRIBUTE_NODISCARD friend auto operator+(difference_type size, group_iterator it) -> group_iterator { - return it += size; + it += size; + return it; } CPPSORT_ATTRIBUTE_NODISCARD friend auto operator-(group_iterator it, difference_type size) -> group_iterator { - return it -= size; + it -= size; + return it; } CPPSORT_ATTRIBUTE_NODISCARD From 6f219f62b7fca8c4ec12e867dd311b87dbbe2409 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 1 Nov 2021 18:10:17 +0100 Subject: [PATCH 52/79] Cleanup heap exhaustion testing code a bit Only the declaration of scoped_memory_exhaustion and its member functions are needed in the header, put everything in the .cpp. This also seems to fix what is probably another thread_local issue with MinGW-w64 10.3. --- testsuite/testing-tools/memory_exhaustion.h | 20 +++----------- testsuite/testing-tools/new_delete.cpp | 30 +++++++++++++++------ 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/testsuite/testing-tools/memory_exhaustion.h b/testsuite/testing-tools/memory_exhaustion.h index 55165e6f..e69ae865 100644 --- a/testsuite/testing-tools/memory_exhaustion.h +++ b/testsuite/testing-tools/memory_exhaustion.h @@ -1,29 +1,15 @@ /* - * Copyright (c) 2019 Morwenn + * Copyright (c) 2019-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_TESTSUITE_MEMORY_EXHAUSTION_H_ #define CPPSORT_TESTSUITE_MEMORY_EXHAUSTION_H_ -//////////////////////////////////////////////////////////// -// Headers -//////////////////////////////////////////////////////////// - -// This variable is defined in new_delete.h -extern thread_local bool heap_memory_exhaustion_should_fail; - // Class to make memory exhaustion fail in the current scope struct scoped_memory_exhaustion { - scoped_memory_exhaustion() noexcept - { - heap_memory_exhaustion_should_fail = true; - } - - ~scoped_memory_exhaustion() - { - heap_memory_exhaustion_should_fail = false; - } + scoped_memory_exhaustion() noexcept; + ~scoped_memory_exhaustion(); }; #endif // CPPSORT_TESTSUITE_MEMORY_EXHAUSTION_H_ diff --git a/testsuite/testing-tools/new_delete.cpp b/testsuite/testing-tools/new_delete.cpp index 5226e7a8..dcec6048 100644 --- a/testsuite/testing-tools/new_delete.cpp +++ b/testsuite/testing-tools/new_delete.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Morwenn + * Copyright (c) 2019-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -7,14 +7,28 @@ #include #include -// -// Replace the global new and delete for the purpose of testing -// algorithms that have fallbacks when they can't allocate -// extra memory -// +//////////////////////////////////////////////////////////// +// Variable to control whether memory exhaustion should fail -// This variable controls whether memory exhaustion should fail -thread_local bool heap_memory_exhaustion_should_fail = false; +static thread_local bool heap_memory_exhaustion_should_fail = false; + +//////////////////////////////////////////////////////////// +// scoped_memory_exhaustion (scope guard) + +scoped_memory_exhaustion::scoped_memory_exhaustion() noexcept +{ + heap_memory_exhaustion_should_fail = true; +} + +scoped_memory_exhaustion::~scoped_memory_exhaustion() +{ + heap_memory_exhaustion_should_fail = false; +} + +//////////////////////////////////////////////////////////// +// Replace the global new and delete functions for the +// purpose of testing that some algorithms still work when +// heap memory allocations fail auto operator new(std::size_t size) -> void* From ee0a894f8baf3e77ceb7256e00141629f968228e Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 4 Nov 2021 00:44:02 +0100 Subject: [PATCH 53/79] Improve the documentation about stable adapters Reorganize, simplify, add a graph to sum up the relations between the different components (and the DOT source code to draw it). --- docs/Sorter-adapters.md | 44 ++++++++++++++++++-------------- docs/images/stable-adapters.png | Bin 0 -> 72615 bytes tools/stable-adapters.dot | 31 ++++++++++++++++++++++ 3 files changed, 56 insertions(+), 19 deletions(-) create mode 100644 docs/images/stable-adapters.png create mode 100644 tools/stable-adapters.dot diff --git a/docs/Sorter-adapters.md b/docs/Sorter-adapters.md index a317ab40..f92d2388 100644 --- a/docs/Sorter-adapters.md +++ b/docs/Sorter-adapters.md @@ -225,51 +225,56 @@ using sorter = cppsort::hybrid_adapter< #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 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. +Those *sorter adapters* are similar in that they all take any *sorter* and produce a *resulting sorter* that is guaranteed to implement a stable sort. From lower level to higher level: +* `make_stable`: artifically make a sorter stable +* `stable_adapter`: main customization point +* `stable_t`: higher-level interface -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. +The *resulting sorter* is always stable and returns the result of the *adapted sorter* if any. -`stable_adapter` and its specializations might expose a `type` member type which aliases the *adapted 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. This `::type` must be constructible from an instance of the *adapted sorter* type. - -The *resulting sorter* is always stable. +```cpp +template +struct make_stable; +``` -`stable_adapter` returns the result of the *adapted sorter* if any. +`make_stable` takes a sorter and artificially alters its behavior to produce a stable sorter. It does so by associating every element of the collection to sort to its starting position and then uses the *adapted sorter* to sort the collection with a special comparator: whenever two elements compare equivalent, it compares the starting positions of the elements to ensure that their relative starting positions are preserved. Storing the starting positions requires O(n) additional space. ```cpp template struct stable_adapter; ``` -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: +`stable_adapter` is the main customization point of those stable sorting facilities, and as such can be specialized to provide stable versions of your own unstable sorters or adapters. **cpp-sort** itself provides `stable_adapter` specializations for the following components: * [`default_sorter`][default-sorter] -* [`std_sorter`][std-sorter] +* [`std_sorter`][std-sorter] (calls [`std::stable_sort`][std-stable-sort] instead of [`std::sort`][std-sort]) * [`verge_sorter`][verge-sorter] * [`hybrid_adapter`][hybrid-adapter] * [`self_sort_adapter`][self-sort-adapter] +* `stable_adapter` itself (automatic unnesting) * [`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. +Specializations of `stable_adapter` must provide an `is_always_stable` member type aliasing [`std::true_type`][std-true-type]. Additionally, they might expose a `type` member type aliasing either the *adapted sorter* or some intermediate sorter which is guaranteed to always be stable (it can also alias the `stable_adapter` specialization itself, but it is generally useless). Its goal is to provide the least nested type that is known to always be stable in order to sometimes skip some template nesting. When present, this `::type` must be constructible from an instance of the *adapted sorter*. -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: +The main `stable_adapter` template uses [`is_stable`][is-stable] when called to check whether the *adapted sorter* produces a stable sorter when called with a given set of parameters. If the call is already stable then th *adapted sorter* is used directly otherwise `make_stable` is used to artificially turn it into a stable sort. ```cpp template -struct make_stable; +using stable_t = /* implementation-defined */; ``` -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. +`stable_t` is the recommended way to obtain a stable sorter from any sorter. Its goal is to alias the "most nested" type that can be used as stable version of the *adapted* sorter. As such it aliases: +* The *adapted sorter* if it is guarnateed to always be stable. +* `stable_adapter::type` otherwise, if such a member type exists. +* `stable_adapter` otherwise. -The header also provides the `stable_t` type alias, which either alias the passed sorter if it is always stable, or `stable_adapter` otherwise. +This little dance sometimes allows to reduce the nesting of function calls and to get better error messages in some places. As such `stable_t` is generally a better alternative to `stable_adapter` from a consumer point of view. -```cpp -template -using stable_t = /* implementation-defined */; -``` +The following graph sums up the relations between the different features of the library linked to stable sorting. The dashed lines represent the aliasing logic of `stable_t`. -*New in version 1.9.0:* `stable_t` +![Relations between the stable sorting features](https://github.com/Morwenn/cpp-sort/wiki/images/stable-adapters.png) -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`. +*New in version 1.9.0:* `stable_t` and `stable_adapter::type` ### `verge_adapter` @@ -308,6 +313,7 @@ When wrapped into [`stable_adapter`][stable-adapter], it has a slightly differen [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 + [std-true-type]: https://en.cppreference.com/w/cpp/types/integral_constant [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/images/stable-adapters.png b/docs/images/stable-adapters.png new file mode 100644 index 0000000000000000000000000000000000000000..58e9d29690fa66deab047b53ef3e922b215bd36b GIT binary patch literal 72615 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 IoWJ@10Lkh9DgXcg literal 0 HcmV?d00001 diff --git a/tools/stable-adapters.dot b/tools/stable-adapters.dot new file mode 100644 index 00000000..fcfd60ec --- /dev/null +++ b/tools/stable-adapters.dot @@ -0,0 +1,31 @@ +// Copyright (c) 2021 Morwenn +// SPDX-License-Identifier: MIT + +digraph G { + + // Nodes + node [fontname="consolas"]; + sorter[label="Sorter"] + stable_t[label="stable_t"] + stable_adapter[label="stable_adapter"] + make_stable[label="make_stable"] + "stable_adapter::type" + "Use specialization" + node [shape="diamond"] + is_always_stable[label="is_always_stable_v"] + is_stable[label="is_stable"] + "stable_adapter::type exists?" + specialized[label="Specialized?"] + + // Flow + stable_t -> is_always_stable[style=dashed] + is_always_stable -> sorter[label="true",fontname="consolas",fontsize="10",style=dashed] + is_always_stable -> "stable_adapter::type exists?"[label="false",fontname="consolas",fontsize="10",style=dashed] + "stable_adapter::type exists?" -> "stable_adapter::type"[label="true",fontname="consolas",fontsize="10"style=dashed] + "stable_adapter::type exists?" -> stable_adapter[label="false",fontname="consolas",fontsize="10"style=dashed] + stable_adapter -> specialized + specialized -> "Use specialization"[label="true",fontname="consolas",fontsize="10"] + specialized -> is_stable[label="false",fontname="consolas",fontsize="10"] + is_stable -> sorter[label="true",fontname="consolas",fontsize="10"] + is_stable -> make_stable[label="false",fontname="consolas",fontsize="10"] +} From 1cd671a96378eb1d99bdffcf9e19fe9c7319235e Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 4 Nov 2021 00:46:58 +0100 Subject: [PATCH 54/79] Remove redundant stable_adapter::type --- include/cpp-sort/adapters/stable_adapter.h | 1 - include/cpp-sort/detail/stable_adapter_self_sort_adapter.h | 1 - include/cpp-sort/sorters/default_sorter.h | 2 -- include/cpp-sort/sorters/std_sorter.h | 2 -- 4 files changed, 6 deletions(-) diff --git a/include/cpp-sort/adapters/stable_adapter.h b/include/cpp-sort/adapters/stable_adapter.h index 88d4d5d5..774137c5 100644 --- a/include/cpp-sort/adapters/stable_adapter.h +++ b/include/cpp-sort/adapters/stable_adapter.h @@ -252,7 +252,6 @@ namespace cppsort // Sorter traits using is_always_stable = std::true_type; - using type = stable_adapter; }; // Accidental nesting can happen, unwrap 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 46e810dd..439ea898 100644 --- a/include/cpp-sort/detail/stable_adapter_self_sort_adapter.h +++ b/include/cpp-sort/detail/stable_adapter_self_sort_adapter.h @@ -115,7 +115,6 @@ 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 592ecc39..09464af1 100644 --- a/include/cpp-sort/sorters/default_sorter.h +++ b/include/cpp-sort/sorters/default_sorter.h @@ -36,8 +36,6 @@ namespace cppsort constexpr explicit stable_adapter(const default_sorter&) noexcept: stable_adapter() {} - - using type = stable_adapter; }; //////////////////////////////////////////////////////////// diff --git a/include/cpp-sort/sorters/std_sorter.h b/include/cpp-sort/sorters/std_sorter.h index 5e9d1ade..3b5797f0 100644 --- a/include/cpp-sort/sorters/std_sorter.h +++ b/include/cpp-sort/sorters/std_sorter.h @@ -112,8 +112,6 @@ namespace cppsort constexpr explicit stable_adapter(std_sorter) noexcept: sorter_facade() {} - - using type = stable_adapter; }; //////////////////////////////////////////////////////////// From 7f4711a1c2b3409dbb21e30fc809b16630e7b319 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 4 Nov 2021 17:56:53 +0100 Subject: [PATCH 55/79] Add README example to examples directory --- README.md | 7 +++---- examples/CMakeLists.txt | 4 ++-- examples/readme_example.cpp | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 examples/readme_example.cpp diff --git a/README.md b/README.md index 4f3d5f0a..c5a646cc 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,6 @@ Here is a more complete example of what can be done with the library: #include #include #include -#include #include #include #include @@ -71,9 +70,9 @@ int main() sorter(vec, std::greater<>{}, &wrapper::value); assert(std::equal( - std::begin(li), std::end(li), - std::begin(vec), std::end(vec), - [](auto& lhs, auto& rhs) { return lhs.value == rhs.value; } + li.begin(), li.end(), + vec.begin(), vec.end(), + [](const auto& lhs, const auto& rhs) { return lhs.value == rhs.value; } )); } ``` diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 8ebf2a3f..1e2bed46 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,10 +1,10 @@ -# Copyright (c) 2019-2020 Morwenn +# Copyright (c) 2019-2021 Morwenn # SPDX-License-Identifier: MIT include(cpp-sort-utils) # Quick & dirty script to compile the examples -foreach(filename bubble_sorter.cpp list_selection_sorter.cpp) +foreach(filename bubble_sorter.cpp list_selection_sorter.cpp readme_example.cpp) get_filename_component(name ${filename} NAME_WE) add_executable(${name} ${filename}) target_link_libraries(${name} PRIVATE cpp-sort::cpp-sort) diff --git a/examples/readme_example.cpp b/examples/readme_example.cpp new file mode 100644 index 00000000..9806ee46 --- /dev/null +++ b/examples/readme_example.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 Morwenn + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include + +int main() +{ + struct wrapper { int value; }; + + std::forward_list li = { {5}, {8}, {3}, {2}, {9} }; + std::vector vec = { {5}, {8}, {3}, {2}, {9} }; + + // When used, this sorter will use a pattern-defeating quicksort + // to sort random-access collections, and a mergesort otherwise + cppsort::hybrid_adapter< + cppsort::pdq_sorter, + cppsort::merge_sorter + > sorter; + + // Sort li and vec in reverse order using their value member + sorter(li, std::greater<>{}, &wrapper::value); + sorter(vec, std::greater<>{}, &wrapper::value); + + assert(std::equal( + li.begin(), li.end(), + vec.begin(), vec.end(), + [](const auto& lhs, const auto& rhs) { return lhs.value == rhs.value; } + )); +} From 902dc43a63f3528eb0a7cbce6e073a7944cf907a Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 4 Nov 2021 19:44:37 +0100 Subject: [PATCH 56/79] Introduce is_specialization_of That type trait replaces the is_std_forward_list and is_std_list traits that were previously used. It is a much more generic and scalable solution to the problem. --- .../detail/container_aware/insertion_sort.h | 5 +-- include/cpp-sort/detail/std_list_traits.h | 43 ------------------- include/cpp-sort/detail/type_traits.h | 21 +++++++++ 3 files changed, 23 insertions(+), 46 deletions(-) delete mode 100644 include/cpp-sort/detail/std_list_traits.h diff --git a/include/cpp-sort/detail/container_aware/insertion_sort.h b/include/cpp-sort/detail/container_aware/insertion_sort.h index 03bd1c39..fec76a8f 100644 --- a/include/cpp-sort/detail/container_aware/insertion_sort.h +++ b/include/cpp-sort/detail/container_aware/insertion_sort.h @@ -20,7 +20,6 @@ #include #include #include "../bitops.h" -#include "../std_list_traits.h" #include "../type_traits.h" #include "../upper_bound.h" @@ -202,8 +201,8 @@ namespace cppsort template< typename First, typename... Args, typename = detail::enable_if_t< - not detail::is_std_list>::value && - not detail::is_std_forward_list>::value + not detail::is_specialization_of_v, std::list> && + not detail::is_specialization_of_v, std::forward_list> > > auto operator()(First&& first, Args&&... args) const diff --git a/include/cpp-sort/detail/std_list_traits.h b/include/cpp-sort/detail/std_list_traits.h deleted file mode 100644 index 0a5f1f57..00000000 --- a/include/cpp-sort/detail/std_list_traits.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2016 Morwenn - * SPDX-License-Identifier: MIT - */ -#ifndef CPPSORT_DETAIL_STD_LIST_TRAITS_H_ -#define CPPSORT_DETAIL_STD_LIST_TRAITS_H_ - -//////////////////////////////////////////////////////////// -// Headers -//////////////////////////////////////////////////////////// -#include -#include -#include - -namespace cppsort -{ -namespace detail -{ - // Small helpers to help disambiguate the standard list - // types in some specific scenarios - - template - struct is_std_list: - std::false_type - {}; - - template - struct is_std_list>: - std::true_type - {}; - - template - struct is_std_forward_list: - std::false_type - {}; - - template - struct is_std_forward_list>: - std::true_type - {}; -}} - -#endif // CPPSORT_DETAIL_STD_LIST_TRAITS_H_ diff --git a/include/cpp-sort/detail/type_traits.h b/include/cpp-sort/detail/type_traits.h index e2346398..97642869 100644 --- a/include/cpp-sort/detail/type_traits.h +++ b/include/cpp-sort/detail/type_traits.h @@ -308,6 +308,27 @@ namespace detail using is_unsigned = std::is_unsigned; #endif + //////////////////////////////////////////////////////////// + // is_specialization_of: check that a given type is a + // specialization of a given class template, with the caveat + // that the class template can only have type template + // parameters + // + // See https://wg21.link/P2098R0 + + template class Template> + struct is_specialization_of: + std::false_type + {}; + + template class Template, typename... Args> + struct is_specialization_of, Template>: + std::true_type + {}; + + template class Template> + constexpr bool is_specialization_of_v = is_specialization_of::value; + //////////////////////////////////////////////////////////// // is_in_pack: check whether a given std::size_t value // appears in a std::size_t... parameter pack From f46d59acf8976c5c7bc6f4aa6cf55eb3e92e88d8 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 7 Nov 2021 22:57:49 +0100 Subject: [PATCH 57/79] Special-case stable_t when T is a stable_adapter speciatization stable_t used T directly when it was a stable_adapter specialization instead of trying to use T::type when such a member type exists, which could inhibit some specializations. This change improves the stable_t in the presence of accidental stable_adapter nesting, which is more and more common as stable_t becomes the main consumer interface. This commit also updates the documentation accordingly, and splits the graph explaining stable adapters into two: one explaining how the stable_adapter works, and another one explaining what stable_t aliases. --- docs/Sorter-adapters.md | 12 +++--- docs/images/stable-adapters.png | Bin 72615 -> 0 bytes docs/images/stable_adapter.png | Bin 0 -> 35352 bytes docs/images/stable_t.png | Bin 0 -> 70739 bytes include/cpp-sort/adapters/stable_adapter.h | 44 +++++++++++++++------ testsuite/adapters/mixed_adapters.cpp | 9 +++++ tools/stable-adapters.dot | 31 --------------- tools/stable_adapter.dot | 22 +++++++++++ tools/stable_t.dot | 29 ++++++++++++++ 9 files changed, 99 insertions(+), 48 deletions(-) delete mode 100644 docs/images/stable-adapters.png create mode 100644 docs/images/stable_adapter.png create mode 100644 docs/images/stable_t.png delete mode 100644 tools/stable-adapters.dot create mode 100644 tools/stable_adapter.dot create mode 100644 tools/stable_t.dot diff --git a/docs/Sorter-adapters.md b/docs/Sorter-adapters.md index f92d2388..ce3c1bd7 100644 --- a/docs/Sorter-adapters.md +++ b/docs/Sorter-adapters.md @@ -258,21 +258,22 @@ Specializations of `stable_adapter` must provide an `is_always_stable` member ty The main `stable_adapter` template uses [`is_stable`][is-stable] when called to check whether the *adapted sorter* produces a stable sorter when called with a given set of parameters. If the call is already stable then th *adapted sorter* is used directly otherwise `make_stable` is used to artificially turn it into a stable sort. +![Visual explanation of what stable_adapter does](https://github.com/Morwenn/cpp-sort/wiki/images/stable_adapter.png) + ```cpp template using stable_t = /* implementation-defined */; ``` -`stable_t` is the recommended way to obtain a stable sorter from any sorter. Its goal is to alias the "most nested" type that can be used as stable version of the *adapted* sorter. As such it aliases: -* The *adapted sorter* if it is guarnateed to always be stable. +`stable_t` is the recommended way to obtain a stable sorter from any sorter. Its goal is to alias the "most nested" type that can be used as stable version of the *adapted sorter*. As such it aliases (let `Sorter` be the *adapted sorter*): +* `Sorter::type` if `Sorter` is a `stable_adapter` specialization and such a member type exists. +* `Sorter` otherwise, if it is guaranteed [to always be stable][is-always-stable]. * `stable_adapter::type` otherwise, if such a member type exists. * `stable_adapter` otherwise. This little dance sometimes allows to reduce the nesting of function calls and to get better error messages in some places. As such `stable_t` is generally a better alternative to `stable_adapter` from a consumer point of view. -The following graph sums up the relations between the different features of the library linked to stable sorting. The dashed lines represent the aliasing logic of `stable_t`. - -![Relations between the stable sorting features](https://github.com/Morwenn/cpp-sort/wiki/images/stable-adapters.png) +![Visual explanation of what stable_t aliases](https://github.com/Morwenn/cpp-sort/wiki/images/stable_t.png) *New in version 1.9.0:* `stable_t` and `stable_adapter::type` @@ -302,6 +303,7 @@ When wrapped into [`stable_adapter`][stable-adapter], it has a slightly differen [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-always-stable]: https://github.com/Morwenn/cpp-sort/wiki/Sorter-traits#is_always_stable [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 diff --git a/docs/images/stable-adapters.png b/docs/images/stable-adapters.png deleted file mode 100644 index 58e9d29690fa66deab047b53ef3e922b215bd36b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 72615 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 IoWJ@10Lkh9DgXcg diff --git a/docs/images/stable_adapter.png b/docs/images/stable_adapter.png new file mode 100644 index 0000000000000000000000000000000000000000..eb710aaf457fbc9dfbf67434a3bd2698e1e378a8 GIT binary patch literal 35352 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≫}>KW#i&|(;$(i`S^ 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�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 literal 0 HcmV?d00001 diff --git a/include/cpp-sort/adapters/stable_adapter.h b/include/cpp-sort/adapters/stable_adapter.h index 774137c5..e7d32325 100644 --- a/include/cpp-sort/adapters/stable_adapter.h +++ b/include/cpp-sort/adapters/stable_adapter.h @@ -278,32 +278,52 @@ namespace cppsort namespace detail { template - struct stable_t_impl_false + struct stable_t_impl_type_or { - // The sorter is not always stable and does not have - // a type member named 'type' - using type = stable_adapter; + using type = Sorter; }; template - struct stable_t_impl_false::type>> + struct stable_t_impl_type_or> { - // The sorter is not always stable but has a type member - // called 'type', use that one - using type = typename stable_adapter::type; + using type = typename Sorter::type; }; - template - struct stable_t_impl + template + struct stable_t_impl_true + { + // The sorter is a stable_adapter specialization, use its ::type + // member if it exists, otherwise use the specialization directly + using type = typename stable_t_impl_type_or::type; + }; + + template + struct stable_t_impl_true { - // The sorter is always stable, alias it directly + // The sorter is always stable but not a specialization of + // stable_adapter, alias it directly using type = Sorter; }; + template + struct stable_t_impl + { + // The sorter is always stable, check whether it is already a + // stable_adapter specialization + using type = typename stable_t_impl_true< + Sorter, + is_specialization_of_v + >::type; + }; + template struct stable_t_impl { - using type = typename stable_t_impl_false::type; + // The sorter is not always stable, wrap it in stable_adapter or + // in something close enough + using type = typename stable_t_impl_type_or< + stable_adapter + >::type; }; } diff --git a/testsuite/adapters/mixed_adapters.cpp b/testsuite/adapters/mixed_adapters.cpp index a0a15135..56dcca23 100644 --- a/testsuite/adapters/mixed_adapters.cpp +++ b/testsuite/adapters/mixed_adapters.cpp @@ -203,3 +203,12 @@ 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 stable_adapter", "[stable_adapter]" ) +{ + // Wrap/nest stable_adapter several times + using sorter = cppsort::stable_adapter; + using nested1 = cppsort::stable_adapter; + + CHECK(( std::is_same, sorter>::value )); +} diff --git a/tools/stable-adapters.dot b/tools/stable-adapters.dot deleted file mode 100644 index fcfd60ec..00000000 --- a/tools/stable-adapters.dot +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2021 Morwenn -// SPDX-License-Identifier: MIT - -digraph G { - - // Nodes - node [fontname="consolas"]; - sorter[label="Sorter"] - stable_t[label="stable_t"] - stable_adapter[label="stable_adapter"] - make_stable[label="make_stable"] - "stable_adapter::type" - "Use specialization" - node [shape="diamond"] - is_always_stable[label="is_always_stable_v"] - is_stable[label="is_stable"] - "stable_adapter::type exists?" - specialized[label="Specialized?"] - - // Flow - stable_t -> is_always_stable[style=dashed] - is_always_stable -> sorter[label="true",fontname="consolas",fontsize="10",style=dashed] - is_always_stable -> "stable_adapter::type exists?"[label="false",fontname="consolas",fontsize="10",style=dashed] - "stable_adapter::type exists?" -> "stable_adapter::type"[label="true",fontname="consolas",fontsize="10"style=dashed] - "stable_adapter::type exists?" -> stable_adapter[label="false",fontname="consolas",fontsize="10"style=dashed] - stable_adapter -> specialized - specialized -> "Use specialization"[label="true",fontname="consolas",fontsize="10"] - specialized -> is_stable[label="false",fontname="consolas",fontsize="10"] - is_stable -> sorter[label="true",fontname="consolas",fontsize="10"] - is_stable -> make_stable[label="false",fontname="consolas",fontsize="10"] -} diff --git a/tools/stable_adapter.dot b/tools/stable_adapter.dot new file mode 100644 index 00000000..93c0f1b1 --- /dev/null +++ b/tools/stable_adapter.dot @@ -0,0 +1,22 @@ +// Copyright (c) 2021 Morwenn +// SPDX-License-Identifier: MIT + +digraph G { + + // Nodes + node [fontname="consolas"]; + sorter[label="Sorter"] + stable_adapter[label="stable_adapter"] + make_stable[label="make_stable"] + use_specialization[label="Use specialization"] + node [shape="diamond"] + is_stable_v[label="is_stable"] + specialized[label="Specialized for Sorter?"] + + // Flow + stable_adapter -> specialized + specialized -> use_specialization[label="true",fontname="consolas",fontsize="10"] + specialized -> is_stable_v[label="false",fontname="consolas",fontsize="10"] + is_stable_v -> sorter[label="true",fontname="consolas",fontsize="10"] + is_stable_v -> make_stable[label="false",fontname="consolas",fontsize="10"] +} diff --git a/tools/stable_t.dot b/tools/stable_t.dot new file mode 100644 index 00000000..b99c83df --- /dev/null +++ b/tools/stable_t.dot @@ -0,0 +1,29 @@ +// Copyright (c) 2021 Morwenn +// SPDX-License-Identifier: MIT + +digraph G { + + // Nodes + node [fontname="consolas"]; + sorter[label="Sorter"] + sorter_type[label="Sorter::type"] + stable_t[label="stable_t"] + stable_adapter[label="stable_adapter"] + stable_adapter_type[label="stable_adapter::type"] + node [shape="diamond"] + is_always_stable[label="is_always_stable_v"] + stable_adapter_type_exists[label="stable_adapter::type exists?"] + sorter_type_exists[label="Sorter::type exists?"] + is_specialization[label="is_specialization_of"] + + // Flow + stable_t -> is_always_stable + is_always_stable -> is_specialization[label="true",fontname="consolas",fontsize="10"] + is_specialization -> sorter[label="false",fontname="consolas",fontsize="10"] + is_specialization -> sorter_type_exists[label="true",fontname="consolas",fontsize="10"] + is_always_stable -> stable_adapter_type_exists[label="false",fontname="consolas",fontsize="10"] + stable_adapter_type_exists -> stable_adapter_type[label="true",fontname="consolas",fontsize="10"] + stable_adapter_type_exists -> stable_adapter[label="false",fontname="consolas",fontsize="10"] + sorter_type_exists -> sorter_type[label="true",fontname="consolas",fontsize="10"] + sorter_type_exists -> sorter[label="false",fontname="consolas",fontsize="10"] +} From 7bbd8434c77355fd3e7e095546b31b1e69e9719a Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 8 Nov 2021 11:23:09 +0100 Subject: [PATCH 58/79] Deeper unwrapping of nested stable_adapter --- include/cpp-sort/adapters/stable_adapter.h | 40 ++++++++++++---------- testsuite/adapters/mixed_adapters.cpp | 4 +++ 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/include/cpp-sort/adapters/stable_adapter.h b/include/cpp-sort/adapters/stable_adapter.h index e7d32325..28eff0d0 100644 --- a/include/cpp-sort/adapters/stable_adapter.h +++ b/include/cpp-sort/adapters/stable_adapter.h @@ -254,24 +254,6 @@ namespace cppsort using is_always_stable = std::true_type; }; - // Accidental nesting can happen, unwrap - template - struct stable_adapter>: - stable_adapter - { - stable_adapter() = default; - - constexpr explicit stable_adapter(const stable_adapter& sorter): - stable_adapter(sorter) - {} - - constexpr explicit stable_adapter(stable_adapter&& sorter): - stable_adapter(std::move(sorter)) - {} - - using type = stable_adapter; - }; - //////////////////////////////////////////////////////////// // stable_t @@ -332,6 +314,28 @@ namespace cppsort Sorter, cppsort::is_always_stable_v >::type; + + //////////////////////////////////////////////////////////// + // stable_adapter> + + // Accidental nesting can happen, in which case we try to + // unwrap as many levels as possible + template + struct stable_adapter>: + stable_adapter + { + stable_adapter() = default; + + constexpr explicit stable_adapter(const stable_adapter& sorter): + stable_adapter(sorter) + {} + + constexpr explicit stable_adapter(stable_adapter&& sorter): + stable_adapter(std::move(sorter)) + {} + + using type = typename detail::stable_t_impl_type_or>::type; + }; } #ifdef CPPSORT_ADAPTERS_HYBRID_ADAPTER_DONE_ diff --git a/testsuite/adapters/mixed_adapters.cpp b/testsuite/adapters/mixed_adapters.cpp index 56dcca23..9c682a14 100644 --- a/testsuite/adapters/mixed_adapters.cpp +++ b/testsuite/adapters/mixed_adapters.cpp @@ -209,6 +209,10 @@ TEST_CASE( "stable_adapter over stable_adapter", "[stable_adapter]" ) // Wrap/nest stable_adapter several times using sorter = cppsort::stable_adapter; using nested1 = cppsort::stable_adapter; + using nested2 = cppsort::stable_adapter; + using nested3 = cppsort::stable_adapter; CHECK(( std::is_same, sorter>::value )); + CHECK(( std::is_same, sorter>::value )); + CHECK(( std::is_same, sorter>::value )); } From 0dc7ab44074013ee0b8fa24f659b6b82cf302d80 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 8 Nov 2021 14:40:26 +0100 Subject: [PATCH 59/79] Clarify stable_adapter documentation a bit --- docs/Sorter-adapters.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Sorter-adapters.md b/docs/Sorter-adapters.md index ce3c1bd7..c55ac080 100644 --- a/docs/Sorter-adapters.md +++ b/docs/Sorter-adapters.md @@ -254,7 +254,7 @@ struct stable_adapter; * `stable_adapter` itself (automatic unnesting) * [`verge_adapter`][verge-adapter] -Specializations of `stable_adapter` must provide an `is_always_stable` member type aliasing [`std::true_type`][std-true-type]. Additionally, they might expose a `type` member type aliasing either the *adapted sorter* or some intermediate sorter which is guaranteed to always be stable (it can also alias the `stable_adapter` specialization itself, but it is generally useless). Its goal is to provide the least nested type that is known to always be stable in order to sometimes skip some template nesting. When present, this `::type` must be constructible from an instance of the *adapted sorter*. +Specializations of `stable_adapter` must provide an `is_always_stable` member type aliasing [`std::true_type`][std-true-type]. Additionally, they might expose a `type` member type aliasing either the *adapted sorter* or some intermediate sorter which is guaranteed to always be stable (it can also alias the `stable_adapter` specialization itself, but it is generally useless). Its goal is to provide the least nested type that is known to always be stable in order to sometimes skip some template nesting. When present, this `::type` must be constructible from an instance of the *adapted sorter* (note however that `stable_adapter::type` does not have to be constructible from an instance of `stable_adapter`!). The main `stable_adapter` template uses [`is_stable`][is-stable] when called to check whether the *adapted sorter* produces a stable sorter when called with a given set of parameters. If the call is already stable then th *adapted sorter* is used directly otherwise `make_stable` is used to artificially turn it into a stable sort. @@ -271,7 +271,7 @@ using stable_t = /* implementation-defined */; * `stable_adapter::type` otherwise, if such a member type exists. * `stable_adapter` otherwise. -This little dance sometimes allows to reduce the nesting of function calls and to get better error messages in some places. As such `stable_t` is generally a better alternative to `stable_adapter` from a consumer point of view. +This little dance sometimes allows to reduce the nesting of function calls and to get better error messages in some places (it notably unwraps nested top-level `stable_adapter`). As such `stable_t` is generally a better alternative to `stable_adapter` from a consumer point of view. ![Visual explanation of what stable_t aliases](https://github.com/Morwenn/cpp-sort/wiki/images/stable_t.png) From 5ec423b4b4fed702371d220e750d065938bc920c Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 8 Nov 2021 14:41:08 +0100 Subject: [PATCH 60/79] Let stable_t unwrap stable_adapter --- include/cpp-sort/adapters/stable_adapter.h | 5 +++ testsuite/adapters/mixed_adapters.cpp | 36 ++++++++++++++++------ 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/include/cpp-sort/adapters/stable_adapter.h b/include/cpp-sort/adapters/stable_adapter.h index 28eff0d0..20ee8d09 100644 --- a/include/cpp-sort/adapters/stable_adapter.h +++ b/include/cpp-sort/adapters/stable_adapter.h @@ -252,6 +252,11 @@ namespace cppsort // Sorter traits using is_always_stable = std::true_type; + using type = detail::conditional_t< + cppsort::is_always_stable_v, + Sorter, + stable_adapter + >; }; //////////////////////////////////////////////////////////// diff --git a/testsuite/adapters/mixed_adapters.cpp b/testsuite/adapters/mixed_adapters.cpp index 9c682a14..6fa670b0 100644 --- a/testsuite/adapters/mixed_adapters.cpp +++ b/testsuite/adapters/mixed_adapters.cpp @@ -206,13 +206,31 @@ TEST_CASE( "stability of counting_adapter over self_sort_adapter", TEST_CASE( "stable_adapter over stable_adapter", "[stable_adapter]" ) { - // Wrap/nest stable_adapter several times - using sorter = cppsort::stable_adapter; - using nested1 = cppsort::stable_adapter; - using nested2 = cppsort::stable_adapter; - using nested3 = cppsort::stable_adapter; - - CHECK(( std::is_same, sorter>::value )); - CHECK(( std::is_same, sorter>::value )); - CHECK(( std::is_same, sorter>::value )); + // Wrap/nest stable_adapter several times and check that + // stable_t always returns the most nested stable sorter + + SECTION( "over unstable sorter" ) + { + using sorter = cppsort::stable_adapter; + using nested1 = cppsort::stable_adapter; + using nested2 = cppsort::stable_adapter; + using nested3 = cppsort::stable_adapter; + + CHECK(( std::is_same, sorter>::value )); + CHECK(( std::is_same, sorter>::value )); + CHECK(( std::is_same, sorter>::value )); + } + + SECTION( "over stable sorter" ) + { + // Wrap/nest stable_adapter several times + using sorter = cppsort::insertion_sorter; + using nested1 = cppsort::stable_adapter; + using nested2 = cppsort::stable_adapter; + using nested3 = cppsort::stable_adapter; + + CHECK(( std::is_same, sorter>::value )); + CHECK(( std::is_same, sorter>::value )); + CHECK(( std::is_same, sorter>::value )); + } } From 30e0d3be3f5e527d1339e6573ba7078eecb96ee8 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 9 Nov 2021 22:14:08 +0100 Subject: [PATCH 61/79] Improve sorter_traits documentation --- docs/Sorter-traits.md | 44 ++++++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/docs/Sorter-traits.md b/docs/Sorter-traits.md index c2f69e0c..b18c6aa7 100644 --- a/docs/Sorter-traits.md +++ b/docs/Sorter-traits.md @@ -106,7 +106,7 @@ constexpr bool is_comparison_projection_sorter_iterator_v ### `sorter_traits` -The class template `sorter_traits` contains information about *sorters* and *[[sorter adapters|Sorter adapters]]* such as the kind of iterators accepted by a sorter and whether it implements or not a stable sorting algorithm. +The class template `sorter_traits` contains information about *[[sorters|Sorters]]* and *[[sorter adapters|Sorter adapters]]* such as the kind of iterators accepted by a sorter and whether it is guaranteed to always sort stably. ```cpp template @@ -114,11 +114,12 @@ struct sorter_traits; ``` This class template peeks into `Sorter` to extract the following types: - * `using iterator_category = typename Sorter::iterator_category;` * `using is_always_stable = typename Sorter::is_always_stable;` -This class is a bit different than the trait classes in the standard library: if one of the types above doesn't exist in the passed sorter, it won't exist in `sorter_traits` either. That means that the traits are not tightly coupled: if a sorter doesn't define `is_always_stable` but defines `iterator_category`, it can still be used in [`hybrid_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#hybrid_adapter); instantiating the corresponding `sorter_traits` won't cause a compile-time error because of the missing `is_always_stable`. +Its behaviour is however a bit different from that of the trait classes in the standard library: if one of the types above doesn't exist in the passed sorter, it won't exist in the corresponding `sorter_traits` specialization either. That means that the traits are not tightly coupled: for example if a sorter doesn't define `is_always_stable` but defines `iterator_category`, it can still be used in [`hybrid_adapter`][hybrid-adapter]; instantiating the corresponding `sorter_traits` won't cause a compile-time error because of the missing `is_always_stable`. + +`sorter_traits` can be specialized for user-defined *sorters* or *sorter adapters*, and those specialization are sometimes the only place where the information exists. As such, `sorter_traits` is the preferred way to query information about a sorter - it's all the more important as **cpp-sort** itself sometimes fully relies on a `sorter_traits` specialization to provide some information. The in-class traits should be seen as convenient short-hands, but `sorter_traits` as the main source of truth - it is considered an error if both exist and differ. ### `iterator_category` @@ -127,9 +128,9 @@ 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](https://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. A sorter that intends to work with those tools must document its iterator category by aliasing one of the standard library [iterator tags][iterator-tags]. -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. +The iterator category of the *resulting sorter* of a [[*sorter adapter*|Sorter adapters]] doesn't always match that of the *adapted sorter*: for example [`heap_sorter`][heap-sorter] only accepts random-access iterators, but it accepts forward iterators when wrapped into [`out_of_place_adapter`][out-of-place-adapter]. ### `is_always_stable` @@ -142,9 +143,9 @@ constexpr bool is_always_stable_v = is_always_stable::value; ``` -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. +This type trait is always either [`std::true_type` or `std::false_type`][integral-constant] and tells whether a sorter is always [stable][stability] or not. This information may be useful in some contexts, and is most notably by [`stable_t`][stable-adapter] to avoid unnecessarily nesting templates when possible. -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). +When a sorter adapter is used, the *resulting sorter* is considered always stable if and only if its stability can be guaranteed, and considered unstable otherwise, even when the *adapted sorter* may be stable (for example, [`self_sort_adapter`][self-sort-adapter]`::is_always_stable` is aliased to `std::false_type` since it is impossible to guarantee the stability of every collection's `sort` method). ### `is_stable` @@ -161,16 +162,16 @@ template constexpr bool is_stable_v = is_stable::value; ``` -This trait is a more flexible version of `is_always_stable`: it tells whether a given sorter will use a stable sorting algorithm when called with a specific set of parameters. I can be used as follows: +This trait is a more flexible version of [`is_always_stable`][is-always-stable]: it tells whether a given sorter uses a stable sorting algorithm when called with a specific set of parameters. It can be used as follows: ```cpp -using sorter = self_sort_adapter; +using sorter = self_sort_adapter; static_assert(is_stable&)>, ""); ``` -[`self_sort_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#self_sort_adapter) is a sorter adapter that checks whether a container can sort itself and, if so, uses the container's sorting method instead of the adapted sorter. As a matter of fact, `std::list::sort` implements a stable sorting algorithm (and there is a tweak in **cpp-sort** to take that information into account), so `is_stable&)>` will inherit from `std::true_type`. However, `is_stable&)>` or `is_stable::iterator, std::list::iterator)>` will inherit from `std::false_type` instead. +[`self_sort_adapter`][self-sort-adapter] is a [[*sorter adapter*|Sorter adapters]] that checks whether a container can sort itself and, if so, uses the container's sorting method instead of the *adapted sorter*. As a matter of fact, [`std::list::sort`][std-list-sort] implements a stable sorting algorithm and **cpp-sort** specializes `is_stable` to take that information into account, so `is_stable_v&)>` is `true` despite `is_always_stable_v` being `false`. However, `is_stable_v&)>` and `is_stable_v::iterator, std::list::iterator)>` remain `false`. -The default version of `is_stable` will use `sorter_traits::is_always_stable` to infer the stability of a sorter, but most sorter adapters have dedicated specializations. These specializations allow [`stable_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#stable_adapter) to sometimes avoid `make_stable` and to instead use the *adapted sorter* directly when it knows that calling it with specific parameters already yields a stable sort. +The default version of `is_stable` uses `sorter_traits::is_always_stable` to infer the stability of a sorter, but most sorter adapters have dedicated specializations. These specializations notably allow [`stable_adapter`][stable-adapter] to sometimes avoid using `make_stable` and to instead use the *adapted sorter* directly when it knows that calling it with specific parameters already yields a stable sort. ### `rebind_iterator_category` @@ -179,7 +180,7 @@ template struct rebind_iterator_category; ``` -This class allows to get a sorter similar to the one passed but with a stricter iterator category. For example, it can generate a sorter whose iterator category is `std::bidirectional_iterator_tag` from another sorter whose iterator category is `std::forward_iterator_tag`. It is mostly useful to make several sorters with the same iterator category work for different iterator categories in an [`hybrid_adapter`](https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#hybrid_adapter). Let's say we have two sorters, `foo_sorter` and `bar_sorter`; both have the iterator category `std::forward_iterator_tag`, but `foo_sorter` is the best to sort forward iterators while `bar_sorter` benefits from some optimizations for bidirectional and random-access iterators. It is possible to use the best of both with the following sorter: +This class allows to get a sorter similar to the one passed but with a stricter [iterator category][iterator-tags]. For example, it can generate a sorter whose iterator category is `std::bidirectional_iterator_tag` from another sorter whose iterator category is `std::forward_iterator_tag`. It is mostly useful to make several sorters with the same iterator category work for different iterator categories in [`hybrid_adapter`][hybrid-adapter]. Let's say we have two sorters, `foo_sorter` and `bar_sorter`; both have the iterator category `std::forward_iterator_tag`, but `foo_sorter` is the best to sort forward iterators while `bar_sorter` benefits from some optimizations for bidirectional and random-access iterators. It is possible to use the best of both with the following sorter: ```cpp using sorter = cppsort::hybrid_adapter< @@ -191,7 +192,7 @@ using sorter = cppsort::hybrid_adapter< >; ``` -The sorter above will pick `foo_sorter` for forward iterators, but will pick `bar_sorter` for bidirectional and random-access iterators. A compile-time error will occur if one tries to rebind to an iterator category that is not derived from the sorter's original iterator category. +The sorter above will pick `foo_sorter` for forward iterators, but `bar_sorter` for bidirectional and random-access iterators. A compile-time error occurs when one tries to rebind a sorter to a less strict iterator category than its own. ### `fixed_sorter_traits` @@ -208,6 +209,19 @@ 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`](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`. +* `domain`: a specialization of [`std::index_sequence`][std-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`](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 +* `is_always_stable`: an alias for [`std::true_type`][std-integral-constant] if every specialization of the fixed-size sorter is guaranteed to always be stable, and `std::false_type` otherwise. + + + [heap-sorter]: https://github.com/Morwenn/cpp-sort/wiki/Sorters#heap_sorter + [hybrid-adapter]: https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#hybrid_adapter + [is-always-stable]: https://github.com/Morwenn/cpp-sort/wiki/Sorter-traits#is_always_stable + [iterator-tags]: https://en.cppreference.com/w/cpp/iterator/iterator_tags + [out-of-place-adapter]: https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#out_of_place_adapter + [self-sort-adapter]: https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#self_sort_adapter + [stability]: https://en.wikipedia.org/wiki/Sorting_algorithm#Stability + [stable-adapter]: https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#stable_adapter-make_stable-and-stable_t + [std-integer-sequence]: https://en.cppreference.com/w/cpp/utility/integer_sequence + [std-integral-constant]: https://en.cppreference.com/w/cpp/types/integral_constant + [std-list-sort]: https://en.cppreference.com/w/cpp/container/list/sort From 8d544167fa7dc59f022e45293f3a15a5bc91a03f Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 13 Nov 2021 15:03:20 +0100 Subject: [PATCH 62/79] Fix links in documentation --- docs/Comparators.md | 2 +- docs/Original-research.md | 2 +- docs/Sorter-adapters.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/Comparators.md b/docs/Comparators.md index 0139fb1a..27eedde5 100644 --- a/docs/Comparators.md +++ b/docs/Comparators.md @@ -132,7 +132,7 @@ The two-parameter version of the customization point calls the three-parameter o [binary-predicate]: https://en.cppreference.com/w/cpp/concept/BinaryPredicate - [branchless-traits]https://github.com/Morwenn/cpp-sort/wiki/Miscellaneous-utilities#branchless-traits + [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 diff --git a/docs/Original-research.md b/docs/Original-research.md index 59495895..ba45c941 100644 --- a/docs/Original-research.md +++ b/docs/Original-research.md @@ -6,7 +6,7 @@ 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][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 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. diff --git a/docs/Sorter-adapters.md b/docs/Sorter-adapters.md index c55ac080..c1e0026d 100644 --- a/docs/Sorter-adapters.md +++ b/docs/Sorter-adapters.md @@ -309,7 +309,7 @@ When wrapped into [`stable_adapter`][stable-adapter], it has a slightly differen [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 + [stable-adapter]: https://github.com/Morwenn/cpp-sort/wiki/Sorter-adapters#stable_adapter-make_stable-and-stable_t [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 From 8d7cdb0dbe7e38f7cf361bc9434a5d8948e2a664 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 13 Nov 2021 15:05:14 +0100 Subject: [PATCH 63/79] Make sorter_traits> always stable This is mostly to harden the library and future dependencies against forgetting to add is_always_stable to every specialization. We keep the typedef in every specialization we ship to make sure that it's impossible to use incorrectly, but this specialization of sorter_traits makes it harder to get things accidentally wrong. --- include/cpp-sort/adapters/stable_adapter.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/include/cpp-sort/adapters/stable_adapter.h b/include/cpp-sort/adapters/stable_adapter.h index 20ee8d09..4472669e 100644 --- a/include/cpp-sort/adapters/stable_adapter.h +++ b/include/cpp-sort/adapters/stable_adapter.h @@ -23,6 +23,7 @@ #include "../detail/checkers.h" #include "../detail/immovable_vector.h" #include "../detail/iterator_traits.h" +#include "../detail/raw_checkers.h" #include "../detail/sized_iterator.h" #include "../detail/type_traits.h" @@ -259,6 +260,15 @@ namespace cppsort >; }; + template + struct sorter_traits>: + detail::raw_check_iterator_category> + { + // Ensure that all user-defined specializations of stable_adapter + // are considered always by default by sorter_traits + using is_always_stable = std::true_type; + }; + //////////////////////////////////////////////////////////// // stable_t From 648845e793c169819b21fc4d5fb9d5e220a7829b Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 15 Nov 2021 15:31:52 +0100 Subject: [PATCH 64/79] merge_insertion_sort: get rid of jacobsthal_diff table Instead of having a table, just recompute the numbers as needed, which is an inexpensive as two shifts and a subtraction per number. This makes no noticeable speed difference, scales more than the table solution, and reduces the size of the produce binaries. --- .../cpp-sort/detail/merge_insertion_sort.h | 56 ++++++++----------- 1 file changed, 23 insertions(+), 33 deletions(-) diff --git a/include/cpp-sort/detail/merge_insertion_sort.h b/include/cpp-sort/detail/merge_insertion_sort.h index fb0023cf..34ea017c 100644 --- a/include/cpp-sort/detail/merge_insertion_sort.h +++ b/include/cpp-sort/detail/merge_insertion_sort.h @@ -8,14 +8,11 @@ //////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// -#include #include -#include #include #include #include #include "attributes.h" -#include "config.h" #include "fixed_size_list.h" #include "functional.h" #include "immovable_vector.h" @@ -24,7 +21,6 @@ #include "scope_exit.h" #include "swap_if.h" #include "swap_ranges.h" -#include "type_traits.h" #include "upper_bound.h" namespace cppsort @@ -283,24 +279,6 @@ namespace detail Compare compare, Projection projection) -> void { - // Cache all the differences between a Jacobsthal number and its - // predecessor that fit in 64 bits, starting with the difference - // between the Jacobsthal numbers 4 and 3 (the previous ones are - // unneeded) - constexpr std::uint_fast64_t jacobsthal_diff[] = { - 2u, 2u, 6u, 10u, 22u, 42u, 86u, 170u, 342u, 682u, 1366u, - 2730u, 5462u, 10922u, 21846u, 43690u, 87382u, 174762u, 349526u, 699050u, - 1398102u, 2796202u, 5592406u, 11184810u, 22369622u, 44739242u, 89478486u, - 178956970u, 357913942u, 715827882u, 1431655766u, 2863311530u, 5726623062u, - 11453246122u, 22906492246u, 45812984490u, 91625968982u, 183251937962u, - 366503875926u, 733007751850u, 1466015503702u, 2932031007402u, 5864062014806u, - 11728124029610u, 23456248059222u, 46912496118442u, 93824992236886u, 187649984473770u, - 375299968947542u, 750599937895082u, 1501199875790165u, 3002399751580331u, - 6004799503160661u, 12009599006321322u, 24019198012642644u, 48038396025285288u, - 96076792050570576u, 192153584101141152u, 384307168202282304u, 768614336404564608u, - 1537228672809129216u, 3074457345618258432u, 6148914691236516864u - }; - auto size = last - first; if (size < 2) return; @@ -373,17 +351,29 @@ namespace detail auto current_it = first; auto current_pend = pend.begin(); - for (int k = 0 ; ; ++k) { - // Should be safe: in this code, std::distance should always return - // a positive number, so there is no risk of comparing funny values - using size_type = std::common_type_t< - std::uint_fast64_t, - typename list_t::difference_type - >; - - // Find next index - auto dist = jacobsthal_diff[k]; - if (dist > static_cast(pend.end() - current_pend)) break; + // At each cycle, we need to find the optimal pend element where to + // start the insertion cycle again in a way that minimizes the number + // of comparisons performed. The indices of the optimal sequence of + // pend elements happens to follow a sequence known as Jacobsthal + // numbers: https://oeis.org/A001045 + // + // Due to the element-per-element nature of the algorithm we are using, + // we don't need to know the position of a pend element, but its + // relative position to the previous one, in other words we don't need + // J(n+1), but J(n+1) - J(n). Thanks to the following equivalence it + // can be computed fairly cheaply: + // J(n+1) = 2^n - J(n) + // J(n+2) - J(n+1) = 2^(n+1) - J(n+1) - J(n+1) + // = 2^(n+1) - 2J(n+1) + // = 2(2^n - J(n+1)) + // = 2(2^n - (2^n - J(n))) + // = 2(2^n - 2^n + Jn) + // = 2J(n) + // By keeping track of powers of two and J(n), J(n+1) - J(n) can thus + // be computed with two shifts and a subtraction. + for (typename list_t::difference_type pow2 = 1, jn = 0, dist = 2; + dist <= pend.end() - current_pend; + pow2 *= 2, jn = pow2 - jn, dist = 2 * jn) { auto it = current_it + dist * 2; auto pe = current_pend + dist; From 9b92259b6cb9ca13053987fa98517b5b37cecb4a Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 15 Nov 2021 18:11:19 +0100 Subject: [PATCH 65/79] Test every sorter with empty/small collections --- testsuite/CMakeLists.txt | 1 + testsuite/every_sorter_small_collections.cpp | 80 ++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 testsuite/every_sorter_small_collections.cpp diff --git a/testsuite/CMakeLists.txt b/testsuite/CMakeLists.txt index faa17843..33bd0ec8 100644 --- a/testsuite/CMakeLists.txt +++ b/testsuite/CMakeLists.txt @@ -114,6 +114,7 @@ add_executable(main-tests every_sorter_no_post_iterator.cpp every_sorter_non_const_compare.cpp every_sorter_rvalue_projection.cpp + every_sorter_small_collections.cpp every_sorter_span.cpp every_sorter_throwing_moves.cpp is_stable.cpp diff --git a/testsuite/every_sorter_small_collections.cpp b/testsuite/every_sorter_small_collections.cpp new file mode 100644 index 00000000..c9dfd989 --- /dev/null +++ b/testsuite/every_sorter_small_collections.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2016-2021 Morwenn + * SPDX-License-Identifier: MIT + */ +#include +#include +#include + +TEMPLATE_TEST_CASE( "test every sorter with small collections", "[sorters]", + cppsort::cartesian_tree_sorter, + cppsort::counting_sorter, + cppsort::drop_merge_sorter, + cppsort::grail_sorter<>, + cppsort::heap_sorter, + cppsort::insertion_sorter, + cppsort::mel_sorter, + cppsort::merge_insertion_sorter, + cppsort::merge_sorter, + cppsort::pdq_sorter, + cppsort::poplar_sorter, + cppsort::quick_merge_sorter, + cppsort::quick_sorter, + cppsort::selection_sorter, + cppsort::ska_sorter, + cppsort::slab_sorter, + cppsort::smooth_sorter, + cppsort::spin_sorter, + cppsort::split_sorter, + cppsort::spread_sorter, + cppsort::std_sorter, + cppsort::tim_sorter, + cppsort::verge_sorter, + cppsort::wiki_sorter<> ) +{ + // Test that all sorters are able to sort empty collections or + // collections with very few elements + TestType sorter; + + SECTION( "empty collection" ) + { + std::vector collection; + sorter(collection); + SUCCEED(); + } + + SECTION( "one element" ) + { + std::vector collection = { 5 }; + sorter(collection); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); + } + + SECTION( "2 elements" ) + { + std::vector collection = { 8, 2 }; + sorter(collection); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); + } + + SECTION( "3 elements" ) + { + std::vector collection = { 5, 6, 3 }; + sorter(collection); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); + } + + SECTION( "5 elements" ) + { + std::vector collection = { 8, 0, 1, 6, 3 }; + sorter(collection); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); + } + + SECTION( "10 element" ) + { + std::vector collection = { 5, 6, 7, 1, 2, 9, 3, 8, 0, 4 }; + sorter(collection); + CHECK( std::is_sorted(collection.begin(), collection.end()) ); + } +} From 9c907d7ca9e8dfb382aef4e86d7bf3aa84b69383 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 17 Nov 2021 20:29:11 +0100 Subject: [PATCH 66/79] CI: fix displaying Valgrind logs on failure --- .github/workflows/build-ubuntu.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index 9c391156..ee45c8d6 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -90,3 +90,9 @@ jobs: run: | ctest -T memcheck -C ${{matrix.config.build_type}} -j 2 find ./Testing/Temporary -name "MemoryChecker.*.log" -size +1300c | xargs cat; + + - name: Show Valgrind logs + if: ${{failure() && matrix.config.valgrind == 'ON'}} + working-directory: ${{runner.workspace}}/build + run: | + find ./Testing/Temporary -name "MemoryChecker.*.log" -size +1300c | xargs cat; From 831f4dacea67b2a97c421a57f3e4fcb23ab44990 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 17 Nov 2021 23:13:36 +0100 Subject: [PATCH 67/79] Fix sorting empty collections (issue #194) merge_insertion_sort and slab_sort failed when sorting an empty collection for the same reason: they tried to create an empty fixed_size_list, which actually causes all knids of issues. The two sorters don't attempt to creat such a list anymore, and an assertion was added to the fixed_size_list constructor to ensure that it never happens. --- include/cpp-sort/detail/fixed_size_list.h | 2 ++ include/cpp-sort/detail/merge_insertion_sort.h | 7 ++++++- include/cpp-sort/detail/slabsort.h | 6 +++++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/include/cpp-sort/detail/fixed_size_list.h b/include/cpp-sort/detail/fixed_size_list.h index 2b1011af..80ef28f8 100644 --- a/include/cpp-sort/detail/fixed_size_list.h +++ b/include/cpp-sort/detail/fixed_size_list.h @@ -147,6 +147,8 @@ namespace detail first_free_(buffer_.get()), capacity_(capacity) { + CPPSORT_ASSERT(capacity > 0); + // Node constructors are noexcept, so we don't need to handle // the cleanup of list nodes that would have been required if // exceptions could have been thrown diff --git a/include/cpp-sort/detail/merge_insertion_sort.h b/include/cpp-sort/detail/merge_insertion_sort.h index 34ea017c..b4496284 100644 --- a/include/cpp-sort/detail/merge_insertion_sort.h +++ b/include/cpp-sort/detail/merge_insertion_sort.h @@ -435,9 +435,14 @@ namespace detail Compare compare, Projection projection) -> void { + auto size = last - first; + if (size < 2) { + return; + } + // Make a node pool big enough to hold all the values using node_type = list_node>; - fixed_size_list_node_pool node_pool(last - first); + fixed_size_list_node_pool node_pool(size); merge_insertion_sort_impl( make_group_iterator(std::move(first), 1), diff --git a/include/cpp-sort/detail/slabsort.h b/include/cpp-sort/detail/slabsort.h index 849de861..ac046e16 100644 --- a/include/cpp-sort/detail/slabsort.h +++ b/include/cpp-sort/detail/slabsort.h @@ -256,8 +256,12 @@ namespace detail Compare compare, Projection projection) -> void { - // Node pool used by all try_melsort invocations auto size = last - first; + if (size < 2) { + return; + } + + // Node pool used by all try_melsort invocations using node_type = slabsort_list_node; fixed_size_list_node_pool node_pool(size); From c3ce2bbdbda19d98609e320f1797f023dd16fddf Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 21 Nov 2021 00:38:02 +0100 Subject: [PATCH 68/79] CI: fail when ctest finds no tests --- .github/workflows/build-macos.yml | 2 +- .github/workflows/build-mingw.yml | 2 +- .github/workflows/build-msvc.yml | 2 +- .github/workflows/build-ubuntu.yml | 7 ++----- .github/workflows/code-coverage.yml | 4 +++- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 10a117a3..85534772 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -64,4 +64,4 @@ jobs: env: CTEST_OUTPUT_ON_FAILURE: 1 working-directory: ${{runner.workspace}}/build - run: ctest -C ${{matrix.config.build_type}} + run: ctest -C ${{matrix.config.build_type}} --no-tests=error diff --git a/.github/workflows/build-mingw.yml b/.github/workflows/build-mingw.yml index d78f9505..0ebcdec7 100644 --- a/.github/workflows/build-mingw.yml +++ b/.github/workflows/build-mingw.yml @@ -51,4 +51,4 @@ jobs: env: CTEST_OUTPUT_ON_FAILURE: 1 working-directory: ${{runner.workspace}}/build - run: ctest -C ${{matrix.build_type}} + run: ctest -C ${{matrix.build_type}} --no-tests=error diff --git a/.github/workflows/build-msvc.yml b/.github/workflows/build-msvc.yml index bb3b05b5..09603a4e 100644 --- a/.github/workflows/build-msvc.yml +++ b/.github/workflows/build-msvc.yml @@ -51,4 +51,4 @@ jobs: env: CTEST_OUTPUT_ON_FAILURE: 1 working-directory: ${{runner.workspace}}/build - run: ctest -C ${{matrix.build_type}} + run: ctest -C ${{matrix.build_type}} --no-tests=error diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index ee45c8d6..ce824030 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -87,12 +87,9 @@ jobs: env: CTEST_OUTPUT_ON_FAILURE: 1 working-directory: ${{runner.workspace}}/build - run: | - ctest -T memcheck -C ${{matrix.config.build_type}} -j 2 - find ./Testing/Temporary -name "MemoryChecker.*.log" -size +1300c | xargs cat; + run: ctest -T memcheck -C ${{matrix.config.build_type}} --no-tests=error -j 2 - name: Show Valgrind logs if: ${{failure() && matrix.config.valgrind == 'ON'}} working-directory: ${{runner.workspace}}/build - run: | - find ./Testing/Temporary -name "MemoryChecker.*.log" -size +1300c | xargs cat; + run: find ./Testing/Temporary -name "MemoryChecker.*.log" -size +1300c | xargs cat diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index e9a1d380..00930489 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -44,8 +44,10 @@ jobs: - name: Run the test suite shell: bash + env: + CTEST_OUTPUT_ON_FAILURE: 1 working-directory: ${{runner.workspace}}/build - run: ctest -C Debug --output-on-failure + run: ctest -C Debug --no-tests=error - name: Capture coverage info shell: bash From 62a4f84a6b899909bd42098e1919de25cebecf73 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Mon, 22 Nov 2021 00:02:08 +0100 Subject: [PATCH 69/79] More thread_local drama (#195) I still don't understand what's wrong with the namespace-scope extern thread_local variables, but even though I wiggled through issues to make it work on other platforms, it doesn't work at all on MSVC. This commit uses a function instead to create a thread_local variable in a way similar to a Meyers singleton. --- testsuite/main.cpp | 6 ------ testsuite/testing-tools/distributions.h | 17 ++++++++++++----- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/testsuite/main.cpp b/testsuite/main.cpp index deb5aa2f..e5c805ba 100644 --- a/testsuite/main.cpp +++ b/testsuite/main.cpp @@ -4,9 +4,3 @@ */ #define CATCH_CONFIG_MAIN #include -#include -#include - -thread_local cppsort::detail::rand_bit_generator dist::gen{ - std::mt19937_64(Catch::rngSeed()) -}; diff --git a/testsuite/testing-tools/distributions.h b/testsuite/testing-tools/distributions.h index 849f8d52..173d3b7c 100644 --- a/testsuite/testing-tools/distributions.h +++ b/testsuite/testing-tools/distributions.h @@ -9,14 +9,21 @@ // Headers //////////////////////////////////////////////////////////// #include +#include #include #include namespace dist { - // Utility allowing to fetch random bits from a URBG one by one, - // defined in main.cpp - extern thread_local cppsort::detail::rand_bit_generator gen; + inline auto gen() + -> cppsort::detail::rand_bit_generator& + { + // Utility allowing to fetch random bits from a URBG one by one + thread_local cppsort::detail::rand_bit_generator res{ + std::mt19937_64(Catch::rngSeed()) + }; + return res; + } template struct distribution @@ -40,7 +47,7 @@ namespace dist auto operator()(OutputIterator out, long long int size, long long int start=0ll) const -> void { - cppsort::detail::fill_with_shuffle(out, size, start, gen); + cppsort::detail::fill_with_shuffle(out, size, start, gen()); } }; @@ -57,7 +64,7 @@ namespace dist auto operator()(OutputIterator out, long long int size) const -> void { - cppsort::detail::fill_with_shuffle(out, size, 0, gen, &mod_16); + cppsort::detail::fill_with_shuffle(out, size, 0, gen(), &mod_16); } }; From 1385003212ef971abee5f2041317285ab33de52a Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 23 Nov 2021 17:49:53 +0100 Subject: [PATCH 70/79] Get rid of simple recursion in introselect --- include/cpp-sort/detail/introselect.h | 92 +++++++++++++-------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/include/cpp-sort/detail/introselect.h b/include/cpp-sort/detail/introselect.h index c28ee5d4..da86ac45 100644 --- a/include/cpp-sort/detail/introselect.h +++ b/include/cpp-sort/detail/introselect.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2020 Morwenn + * Copyright (c) 2018-2021 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_INTROSELECT_H_ @@ -274,52 +274,52 @@ namespace detail auto&& comp = utility::as_function(compare); auto&& proj = utility::as_function(projection); - if (size <= 32) { - small_sort(first, last, size, std::move(compare), std::move(projection)); - return std::next(first, nth_pos); - } - - // Choose pivot as either median of 9 or median of medians - auto temp = pick_pivot(first, last, size, bad_allowed, compare, projection); - auto median_it = temp.first; - auto last_1 = temp.second; - - // Put the pivot at position std::prev(last) and partition - iter_swap(median_it, last_1); - auto&& pivot1 = proj(*last_1); - auto middle1 = detail::partition( - first, last_1, - [&](auto&& elem) { return comp(proj(elem), pivot1); } - ); - - // Put the pivot in its final position and partition - iter_swap(middle1, last_1); - auto&& pivot2 = proj(*middle1); - auto middle2 = detail::partition( - std::next(middle1), last, - [&](auto&& elem) { return not comp(pivot2, proj(elem)); } - ); - - // Recursive call: heuristic trick here: in real world cases, - // the middle partition is more likely to be smaller than the - // right one, so computing its size should generally be cheaper - auto size_left = std::distance(first, middle1); - auto size_middle = std::distance(middle1, middle2); - auto size_right = size - size_left - size_middle; - - // TODO: unroll tail recursion - // We're done if the nth element is in the middle partition - if (nth_pos < size_left) { - return introselect(first, middle1, nth_pos, - size_left, --bad_allowed, - std::move(compare), std::move(projection)); - } else if (nth_pos > size_left + size_middle) { - return introselect(middle2, last, nth_pos - size_left - size_middle, - size_right, --bad_allowed, - std::move(compare), std::move(projection)); + while (size > 32) { + // Choose pivot as either median of 9 or median of medians + auto temp = pick_pivot(first, last, size, bad_allowed, compare, projection); + auto median_it = temp.first; + auto last_1 = temp.second; + + // Put the pivot at position std::prev(last) and partition + iter_swap(median_it, last_1); + auto&& pivot1 = proj(*last_1); + auto middle1 = detail::partition( + first, last_1, + [&](auto&& elem) { return comp(proj(elem), pivot1); } + ); + + // Put the pivot in its final position and partition + iter_swap(middle1, last_1); + auto&& pivot2 = proj(*middle1); + auto middle2 = detail::partition( + std::next(middle1), last, + [&](auto&& elem) { return not comp(pivot2, proj(elem)); } + ); + + // Recursive call: heuristic trick here: in real world cases, + // the middle partition is more likely to be smaller than the + // right one, so computing its size should generally be cheaper + auto size_left = std::distance(first, middle1); + auto size_middle = std::distance(middle1, middle2); + auto size_right = size - size_left - size_middle; + + // We're done if the nth element is in the middle partition + if (nth_pos < size_left) { + last = middle1; + size = size_left; + } else if (nth_pos > size_left + size_middle) { + first = middle2; + nth_pos -= size_left + size_middle; + size = size_right; + } else { + // Return an iterator to the nth element + return std::next(middle1, nth_pos - size_left); + } + --bad_allowed; } - // Return an iterator to the nth element - return std::next(middle1, nth_pos - size_left); + // Fallback when the collection is small enough + small_sort(first, last, size, std::move(compare), std::move(projection)); + return std::next(first, nth_pos); } }} From b8eee3afd0762efc949c38b441c47069e899afb1 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 23 Nov 2021 18:52:00 +0100 Subject: [PATCH 71/79] Compute bad_allowed directly in introselect --- include/cpp-sort/detail/introselect.h | 9 +++++---- include/cpp-sort/detail/nth_element.h | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/include/cpp-sort/detail/introselect.h b/include/cpp-sort/detail/introselect.h index da86ac45..1e883d1b 100644 --- a/include/cpp-sort/detail/introselect.h +++ b/include/cpp-sort/detail/introselect.h @@ -146,7 +146,7 @@ namespace detail template auto introselect(ForwardIterator first, ForwardIterator last, difference_type_t nth_pos, - difference_type_t size, int bad_allowed, + difference_type_t size, Compare compare, Projection projection) -> ForwardIterator; @@ -197,7 +197,7 @@ namespace detail size = rounded_size == size ? size / 5 : size / 5 + 1; // Mutual recursion with introselect - return introselect(first, last, size / 2, size, detail::log2(size), + return introselect(first, last, size / 2, size, std::move(compare), std::move(projection)); } @@ -260,12 +260,12 @@ namespace detail } //////////////////////////////////////////////////////////// - // Forward nth_element based on introselect + // Introselect template auto introselect(ForwardIterator first, ForwardIterator last, difference_type_t nth_pos, - difference_type_t size, int bad_allowed, + difference_type_t size, Compare compare, Projection projection) -> ForwardIterator { @@ -273,6 +273,7 @@ namespace detail auto&& comp = utility::as_function(compare); auto&& proj = utility::as_function(projection); + int bad_allowed = detail::log2(size); while (size > 32) { // Choose pivot as either median of 9 or median of medians diff --git a/include/cpp-sort/detail/nth_element.h b/include/cpp-sort/detail/nth_element.h index 4c8db6a8..2d2b9e98 100644 --- a/include/cpp-sort/detail/nth_element.h +++ b/include/cpp-sort/detail/nth_element.h @@ -29,7 +29,7 @@ namespace detail Compare compare, Projection projection) -> ForwardIterator { - return introselect(first, last, nth_pos, size, detail::log2(size), + return introselect(first, last, nth_pos, size, std::move(compare), std::move(projection)); } From 2726f11f629f688ffa5af6495c09e169eaa1ed08 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 25 Nov 2021 17:38:05 +0100 Subject: [PATCH 72/79] Remove double parentheses in CHECK[_FALSE] It was a remnant of old versions of Catch where passing an expression with commas outside of inner parentheses was not possible, which typically happened with explicit template parameters. The only ones left are due to other reason, such as Catch2 not wanting to decompose some expressions, the typical suspect being ||. --- .../adapters/hybrid_adapter_is_stable.cpp | 72 ++++++------ testsuite/adapters/mixed_adapters.cpp | 68 +++++------ testsuite/is_stable.cpp | 106 +++++++++--------- testsuite/sorters/ska_sorter.cpp | 14 +-- testsuite/utility/branchless_traits.cpp | 70 ++++++------ 5 files changed, 166 insertions(+), 164 deletions(-) diff --git a/testsuite/adapters/hybrid_adapter_is_stable.cpp b/testsuite/adapters/hybrid_adapter_is_stable.cpp index 876f9368..be978811 100644 --- a/testsuite/adapters/hybrid_adapter_is_stable.cpp +++ b/testsuite/adapters/hybrid_adapter_is_stable.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2018 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -31,17 +31,17 @@ TEST_CASE( "hybrid_adapter stability checks", cppsort::pdq_sorter >; - CHECK(( not cppsort::is_stable&)>::value )); - CHECK(( not cppsort::is_stable::iterator, - std::vector::iterator)>::value )); + CHECK( not cppsort::is_stable&)>::value ); + CHECK( not cppsort::is_stable::iterator, + std::vector::iterator)>::value ); - CHECK(( cppsort::is_stable&)>::value )); - CHECK(( cppsort::is_stable::iterator, - std::list::iterator)>::value )); + CHECK( cppsort::is_stable&)>::value ); + CHECK( cppsort::is_stable::iterator, + std::list::iterator)>::value ); - CHECK(( not cppsort::is_stable&)>::value )); - CHECK(( not cppsort::is_stable::iterator, - std::forward_list::iterator)>::value )); + CHECK( not cppsort::is_stable&)>::value ); + CHECK( not cppsort::is_stable::iterator, + std::forward_list::iterator)>::value ); } SECTION( "nested hybrid_adapter" ) @@ -57,17 +57,17 @@ TEST_CASE( "hybrid_adapter stability checks", cppsort::pdq_sorter >; - CHECK(( not cppsort::is_stable&)>::value )); - CHECK(( not cppsort::is_stable::iterator, - std::vector::iterator)>::value )); + CHECK( not cppsort::is_stable&)>::value ); + CHECK( not cppsort::is_stable::iterator, + std::vector::iterator)>::value ); - CHECK(( cppsort::is_stable&)>::value )); - CHECK(( cppsort::is_stable::iterator, - std::list::iterator)>::value )); + CHECK( cppsort::is_stable&)>::value ); + CHECK( cppsort::is_stable::iterator, + std::list::iterator)>::value ); - CHECK(( not cppsort::is_stable&)>::value )); - CHECK(( not cppsort::is_stable::iterator, - std::forward_list::iterator)>::value )); + CHECK( not cppsort::is_stable&)>::value ); + CHECK( not cppsort::is_stable::iterator, + std::forward_list::iterator)>::value ); } SECTION( "with small_array_adapter" ) @@ -80,27 +80,27 @@ TEST_CASE( "hybrid_adapter stability checks", cppsort::merge_sorter >; - CHECK(( cppsort::is_stable&)>::value )); - CHECK(( cppsort::is_stable::iterator, - std::vector::iterator)>::value )); + CHECK( cppsort::is_stable&)>::value ); + CHECK( cppsort::is_stable::iterator, + std::vector::iterator)>::value ); - CHECK(( cppsort::is_stable&)>::value )); - CHECK(( cppsort::is_stable::iterator, - std::list::iterator)>::value )); + CHECK( cppsort::is_stable&)>::value ); + CHECK( cppsort::is_stable::iterator, + std::list::iterator)>::value ); - CHECK(( cppsort::is_stable&)>::value )); - CHECK(( cppsort::is_stable::iterator, - std::forward_list::iterator)>::value )); + CHECK( cppsort::is_stable&)>::value ); + CHECK( cppsort::is_stable::iterator, + std::forward_list::iterator)>::value ); - CHECK(( not cppsort::is_stable&)>::value )); - CHECK(( cppsort::is_stable::iterator, - std::array::iterator)>::value )); + CHECK( not cppsort::is_stable&)>::value ); + CHECK( cppsort::is_stable::iterator, + std::array::iterator)>::value ); - CHECK(( cppsort::is_stable&)>::value )); - CHECK(( cppsort::is_stable::iterator, - std::array::iterator)>::value )); + CHECK( cppsort::is_stable&)>::value ); + CHECK( cppsort::is_stable::iterator, + std::array::iterator)>::value ); - CHECK(( not cppsort::is_stable::value )); - CHECK(( cppsort::is_stable::value )); + CHECK( not cppsort::is_stable::value ); + CHECK( cppsort::is_stable::value ); } } diff --git a/testsuite/adapters/mixed_adapters.cpp b/testsuite/adapters/mixed_adapters.cpp index 6fa670b0..8bf4aee5 100644 --- a/testsuite/adapters/mixed_adapters.cpp +++ b/testsuite/adapters/mixed_adapters.cpp @@ -174,33 +174,35 @@ TEST_CASE( "stability of counting_adapter over self_sort_adapter", SECTION( "is_stable" ) { - CHECK( cppsort::is_stable&)>::value ); - CHECK( not cppsort::is_stable&)>::value ); - CHECK( cppsort::is_stable&, std::greater<>)>::value ); - CHECK( not cppsort::is_stable&, std::greater<>)>::value ); - CHECK( not cppsort::is_stable&, std::negate<>)>::value ); - CHECK( not cppsort::is_stable&, std::negate<>)>::value ); - - CHECK( not cppsort::is_stable::iterator, std::list::iterator)>::value ); - CHECK( not cppsort::is_stable::iterator, std::vector::iterator)>::value ); - CHECK( not cppsort::is_stable::iterator, std::list::iterator, std::greater<>)>::value ); - CHECK( not cppsort::is_stable::iterator, std::vector::iterator, std::greater<>)>::value ); - CHECK( not cppsort::is_stable::iterator, std::list::iterator, std::negate<>)>::value ); - CHECK( not cppsort::is_stable::iterator, std::vector::iterator, std::negate<>)>::value ); - - CHECK( cppsort::is_stable&)>::value ); - CHECK( cppsort::is_stable&)>::value ); - CHECK( cppsort::is_stable&, std::greater<>)>::value ); - CHECK( cppsort::is_stable&, std::greater<>)>::value ); - CHECK( cppsort::is_stable&, std::negate<>)>::value ); - CHECK( cppsort::is_stable&, std::negate<>)>::value ); - - CHECK( cppsort::is_stable::iterator, std::list::iterator)>::value ); - CHECK( cppsort::is_stable::iterator, std::vector::iterator)>::value ); - CHECK( cppsort::is_stable::iterator, std::list::iterator, std::greater<>)>::value ); - CHECK( cppsort::is_stable::iterator, std::vector::iterator, std::greater<>)>::value ); - CHECK( cppsort::is_stable::iterator, std::list::iterator, std::negate<>)>::value ); - CHECK( cppsort::is_stable::iterator, std::vector::iterator, std::negate<>)>::value ); + using cppsort::is_stable; + + CHECK( is_stable&)>::value ); + CHECK( not is_stable&)>::value ); + CHECK( is_stable&, std::greater<>)>::value ); + CHECK( not is_stable&, std::greater<>)>::value ); + CHECK( not is_stable&, std::negate<>)>::value ); + CHECK( not is_stable&, std::negate<>)>::value ); + + CHECK( not is_stable::iterator, std::list::iterator)>::value ); + CHECK( not is_stable::iterator, std::vector::iterator)>::value ); + CHECK( not is_stable::iterator, std::list::iterator, std::greater<>)>::value ); + CHECK( not is_stable::iterator, std::vector::iterator, std::greater<>)>::value ); + CHECK( not is_stable::iterator, std::list::iterator, std::negate<>)>::value ); + CHECK( not is_stable::iterator, std::vector::iterator, std::negate<>)>::value ); + + CHECK( is_stable&)>::value ); + CHECK( is_stable&)>::value ); + CHECK( is_stable&, std::greater<>)>::value ); + CHECK( is_stable&, std::greater<>)>::value ); + CHECK( is_stable&, std::negate<>)>::value ); + CHECK( is_stable&, std::negate<>)>::value ); + + CHECK( is_stable::iterator, std::list::iterator)>::value ); + CHECK( is_stable::iterator, std::vector::iterator)>::value ); + CHECK( is_stable::iterator, std::list::iterator, std::greater<>)>::value ); + CHECK( is_stable::iterator, std::vector::iterator, std::greater<>)>::value ); + CHECK( is_stable::iterator, std::list::iterator, std::negate<>)>::value ); + CHECK( is_stable::iterator, std::vector::iterator, std::negate<>)>::value ); } } @@ -216,9 +218,9 @@ TEST_CASE( "stable_adapter over stable_adapter", "[stable_adapter]" ) using nested2 = cppsort::stable_adapter; using nested3 = cppsort::stable_adapter; - CHECK(( std::is_same, sorter>::value )); - CHECK(( std::is_same, sorter>::value )); - CHECK(( std::is_same, sorter>::value )); + CHECK( std::is_same, sorter>::value ); + CHECK( std::is_same, sorter>::value ); + CHECK( std::is_same, sorter>::value ); } SECTION( "over stable sorter" ) @@ -229,8 +231,8 @@ TEST_CASE( "stable_adapter over stable_adapter", "[stable_adapter]" ) using nested2 = cppsort::stable_adapter; using nested3 = cppsort::stable_adapter; - CHECK(( std::is_same, sorter>::value )); - CHECK(( std::is_same, sorter>::value )); - CHECK(( std::is_same, sorter>::value )); + CHECK( std::is_same, sorter>::value ); + CHECK( std::is_same, sorter>::value ); + CHECK( std::is_same, sorter>::value ); } } diff --git a/testsuite/is_stable.cpp b/testsuite/is_stable.cpp index 9fcfaa43..8ac53e45 100644 --- a/testsuite/is_stable.cpp +++ b/testsuite/is_stable.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2018 Morwenn + * Copyright (c) 2016-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -24,80 +24,80 @@ TEST_CASE( "test is_stable with raw sorters", { using sorter = cppsort::merge_sorter; - CHECK(( is_stable&)>::value )); - CHECK(( is_stable::iterator, std::vector::iterator)>::value )); + CHECK( is_stable&)>::value ); + CHECK( is_stable::iterator, std::vector::iterator)>::value ); - CHECK(( is_stable&, std::less<>)>::value )); - CHECK(( is_stable::iterator, std::vector::iterator, - std::less<>)>::value )); + CHECK( is_stable&, std::less<>)>::value ); + CHECK( is_stable::iterator, std::vector::iterator, + std::less<>)>::value ); - CHECK(( is_stable&, std::greater<>)>::value )); - CHECK(( is_stable::iterator, std::vector::iterator, - std::greater<>)>::value )); + CHECK( is_stable&, std::greater<>)>::value ); + CHECK( is_stable::iterator, std::vector::iterator, + std::greater<>)>::value ); - CHECK(( is_stable&, identity)>::value )); - CHECK(( is_stable::iterator, std::vector::iterator, - identity)>::value )); + CHECK( is_stable&, identity)>::value ); + CHECK( is_stable::iterator, std::vector::iterator, + identity)>::value ); - CHECK(( is_stable&, std::negate<>)>::value )); - CHECK(( is_stable::iterator, std::vector::iterator, - std::negate<>)>::value )); + CHECK( is_stable&, std::negate<>)>::value ); + CHECK( is_stable::iterator, std::vector::iterator, + std::negate<>)>::value ); - CHECK(( is_stable&, std::less<>, identity)>::value )); - CHECK(( is_stable::iterator, std::vector::iterator, - std::less<>, identity)>::value )); + CHECK( is_stable&, std::less<>, identity)>::value ); + CHECK( is_stable::iterator, std::vector::iterator, + std::less<>, identity)>::value ); - CHECK(( is_stable&, std::greater<>, identity)>::value )); - CHECK(( is_stable::iterator, std::vector::iterator, - std::greater<>, identity)>::value )); + CHECK( is_stable&, std::greater<>, identity)>::value ); + CHECK( is_stable::iterator, std::vector::iterator, + std::greater<>, identity)>::value ); - CHECK(( is_stable&, std::less<>, std::negate<>)>::value )); - CHECK(( is_stable::iterator, std::vector::iterator, - std::less<>, std::negate<>)>::value )); + CHECK( is_stable&, std::less<>, std::negate<>)>::value ); + CHECK( is_stable::iterator, std::vector::iterator, + std::less<>, std::negate<>)>::value ); - CHECK(( is_stable&, std::greater<>, std::negate<>)>::value )); - CHECK(( is_stable::iterator, std::vector::iterator, - std::greater<>, std::negate<>)>::value )); + CHECK( is_stable&, std::greater<>, std::negate<>)>::value ); + CHECK( is_stable::iterator, std::vector::iterator, + std::greater<>, std::negate<>)>::value ); } SECTION( "quick_sorter" ) { using sorter = cppsort::quick_sorter; - CHECK(( not is_stable&)>::value )); - CHECK(( not is_stable::iterator, std::vector::iterator)>::value )); + CHECK( not is_stable&)>::value ); + CHECK( not is_stable::iterator, std::vector::iterator)>::value ); - CHECK(( not is_stable&, std::less<>)>::value )); - CHECK(( not is_stable::iterator, std::vector::iterator, - std::less<>)>::value )); + CHECK( not is_stable&, std::less<>)>::value ); + CHECK( not is_stable::iterator, std::vector::iterator, + std::less<>)>::value ); - CHECK(( not is_stable&, std::greater<>)>::value )); - CHECK(( not is_stable::iterator, std::vector::iterator, - std::greater<>)>::value )); + CHECK( not is_stable&, std::greater<>)>::value ); + CHECK( not is_stable::iterator, std::vector::iterator, + std::greater<>)>::value ); - CHECK(( not is_stable&, identity)>::value )); - CHECK(( not is_stable::iterator, std::vector::iterator, - identity)>::value )); + CHECK( not is_stable&, identity)>::value ); + CHECK( not is_stable::iterator, std::vector::iterator, + identity)>::value ); - CHECK(( not is_stable&, std::negate<>)>::value )); - CHECK(( not is_stable::iterator, std::vector::iterator, - std::negate<>)>::value )); + CHECK( not is_stable&, std::negate<>)>::value ); + CHECK( not is_stable::iterator, std::vector::iterator, + std::negate<>)>::value ); - CHECK(( not is_stable&, std::less<>, identity)>::value )); - CHECK(( not is_stable::iterator, std::vector::iterator, - std::less<>, identity)>::value )); + CHECK( not is_stable&, std::less<>, identity)>::value ); + CHECK( not is_stable::iterator, std::vector::iterator, + std::less<>, identity)>::value ); - CHECK(( not is_stable&, std::greater<>, identity)>::value )); - CHECK(( not is_stable::iterator, std::vector::iterator, - std::greater<>, identity)>::value )); + CHECK( not is_stable&, std::greater<>, identity)>::value ); + CHECK( not is_stable::iterator, std::vector::iterator, + std::greater<>, identity)>::value ); - CHECK(( not is_stable&, std::less<>, std::negate<>)>::value )); - CHECK(( not is_stable::iterator, std::vector::iterator, - std::less<>, std::negate<>)>::value )); + CHECK( not is_stable&, std::less<>, std::negate<>)>::value ); + CHECK( not is_stable::iterator, std::vector::iterator, + std::less<>, std::negate<>)>::value ); - CHECK(( not is_stable&, std::greater<>, std::negate<>)>::value )); - CHECK(( not is_stable::iterator, std::vector::iterator, - std::greater<>, std::negate<>)>::value )); + CHECK( not is_stable&, std::greater<>, std::negate<>)>::value ); + CHECK( not is_stable::iterator, std::vector::iterator, + std::greater<>, std::negate<>)>::value ); } SECTION( "low_comparisons_sorter" ) diff --git a/testsuite/sorters/ska_sorter.cpp b/testsuite/sorters/ska_sorter.cpp index 8bd02722..4c75cb83 100644 --- a/testsuite/sorters/ska_sorter.cpp +++ b/testsuite/sorters/ska_sorter.cpp @@ -152,14 +152,14 @@ TEST_CASE( "is_ska_sortable", "[ska_sorter]" ) SECTION( "pairs and tuples" ) { // std::pair - CHECK(( is_ska_sortable> )); - CHECK(( is_ska_sortable>> )); - CHECK(( is_ska_sortable>, std::deque>> )); - CHECK_FALSE(( is_ska_sortable>, std::deque>> )); + CHECK( is_ska_sortable> ); + CHECK( is_ska_sortable>> ); + CHECK( is_ska_sortable>, std::deque>> ); + CHECK_FALSE( is_ska_sortable>, std::deque>> ); // std::tuple - CHECK(( is_ska_sortable> )); - CHECK(( is_ska_sortable>> )); - CHECK_FALSE(( is_ska_sortable, std::deque>> )); + CHECK( is_ska_sortable> ); + CHECK( is_ska_sortable>> ); + CHECK_FALSE( is_ska_sortable, std::deque>> ); } } diff --git a/testsuite/utility/branchless_traits.cpp b/testsuite/utility/branchless_traits.cpp index 00c72075..00010ed3 100644 --- a/testsuite/utility/branchless_traits.cpp +++ b/testsuite/utility/branchless_traits.cpp @@ -18,32 +18,32 @@ TEST_CASE( "test that some specific comparisons are branchless", SECTION( "standard library function objects" ) { - CHECK(( is_probably_branchless_comparison_v, int> )); - CHECK(( is_probably_branchless_comparison_v, int> )); - CHECK(( is_probably_branchless_comparison_v, long double> )); + 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 )); + CHECK( is_probably_branchless_comparison_v ); + CHECK( is_probably_branchless_comparison_v ); #endif - CHECK(( is_probably_branchless_comparison_v, int> )); - CHECK(( is_probably_branchless_comparison_v, int> )); - CHECK(( is_probably_branchless_comparison_v, long double> )); + 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 )); + CHECK( is_probably_branchless_comparison_v ); + CHECK( is_probably_branchless_comparison_v ); #endif - CHECK_FALSE(( is_probably_branchless_comparison_v, std::string> )); - CHECK_FALSE(( is_probably_branchless_comparison_v, std::string> )); + 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 )); + 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> )); + 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 )); + CHECK_FALSE( is_probably_branchless_comparison_v ); #endif } @@ -53,26 +53,26 @@ 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_v )); - CHECK(( is_probably_branchless_comparison_v )); - CHECK(( is_probably_branchless_comparison_v )); + CHECK( is_probably_branchless_comparison_v ); + CHECK( is_probably_branchless_comparison_v ); + CHECK( is_probably_branchless_comparison_v ); - CHECK(( is_probably_branchless_comparison_v )); - CHECK_FALSE(( is_probably_branchless_comparison_v )); - CHECK_FALSE(( is_probably_branchless_comparison_v )); + 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_v )); - CHECK_FALSE(( 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_v ); + CHECK_FALSE( is_probably_branchless_comparison_v ); } SECTION( "cv-qualified and reference-qualified types" ) { - CHECK(( is_probably_branchless_comparison_v, const int> )); - CHECK(( is_probably_branchless_comparison_v&, int> )); - CHECK(( is_probably_branchless_comparison_v, int&&> )); + 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 )); + CHECK( is_probably_branchless_comparison_v ); #endif } } @@ -88,16 +88,16 @@ TEST_CASE( "test that some specific projections are branchless", int bar() { return 0; } }; - CHECK(( is_probably_branchless_projection_v )); + CHECK( is_probably_branchless_projection_v ); #if CPPSORT_STD_IDENTITY_AVAILABLE - CHECK(( is_probably_branchless_projection_v )); + 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_v ); + CHECK_FALSE( is_probably_branchless_projection_v ); #if defined(__GLIBCXX__) || defined(_LIBCPP_VERSION) - CHECK(( is_probably_branchless_projection_v )); + CHECK( is_probably_branchless_projection_v ); #endif - CHECK_FALSE(( is_probably_branchless_projection_v )); + CHECK_FALSE( is_probably_branchless_projection_v ); } From 1c3aa37a6a62839870ba9e5cfcf31636079c15d1 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sat, 27 Nov 2021 12:14:06 +0100 Subject: [PATCH 73/79] Make the test suite compile without warnings with /W2 The library itself has compiled without warnings with /W2 for a long time, but the test suite was litered with conversion warnings that weren't errors but required some design tweaks in distributions. This commit solves those issues and finally makes the warnings disappear. --- .../container_aware_adapter_forward_list.cpp | 2 +- .../adapters/container_aware_adapter_list.cpp | 2 +- .../indirect_adapter_every_sorter.cpp | 6 +- .../schwartz_adapter_every_sorter.cpp | 8 +- ...schwartz_adapter_every_sorter_reversed.cpp | 4 +- .../adapters/verge_adapter_every_sorter.cpp | 2 +- testsuite/every_sorter_move_only.cpp | 2 +- testsuite/every_sorter_no_post_iterator.cpp | 4 +- testsuite/sorters/ska_sorter.cpp | 4 +- testsuite/sorters/ska_sorter_projection.cpp | 8 +- testsuite/sorters/spread_sorter.cpp | 6 +- .../sorters/spread_sorter_projection.cpp | 8 +- testsuite/testing-tools/distributions.h | 81 +++++++++++-------- 13 files changed, 76 insertions(+), 61 deletions(-) diff --git a/testsuite/adapters/container_aware_adapter_forward_list.cpp b/testsuite/adapters/container_aware_adapter_forward_list.cpp index 9e5da81e..5a7be4e0 100644 --- a/testsuite/adapters/container_aware_adapter_forward_list.cpp +++ b/testsuite/adapters/container_aware_adapter_forward_list.cpp @@ -23,7 +23,7 @@ TEST_CASE( "container_aware_adapter and std::forward_list", std::vector vec; vec.reserve(187); auto distribution = dist::shuffled{}; - distribution(std::back_inserter(vec), 187, -24.0); + distribution.call(std::back_inserter(vec), 187, -24); SECTION( "insertion_sorter" ) { diff --git a/testsuite/adapters/container_aware_adapter_list.cpp b/testsuite/adapters/container_aware_adapter_list.cpp index 81e91ee3..e088b501 100644 --- a/testsuite/adapters/container_aware_adapter_list.cpp +++ b/testsuite/adapters/container_aware_adapter_list.cpp @@ -23,7 +23,7 @@ TEST_CASE( "container_aware_adapter and std::list", std::vector vec; vec.reserve(187); auto distribution = dist::shuffled{}; - distribution(std::back_inserter(vec), 187, -24.0); + distribution.call(std::back_inserter(vec), 187, -24); SECTION( "insertion_sorter" ) { diff --git a/testsuite/adapters/indirect_adapter_every_sorter.cpp b/testsuite/adapters/indirect_adapter_every_sorter.cpp index feb6ab4f..2dc44246 100644 --- a/testsuite/adapters/indirect_adapter_every_sorter.cpp +++ b/testsuite/adapters/indirect_adapter_every_sorter.cpp @@ -40,7 +40,7 @@ TEMPLATE_TEST_CASE( "every random-access sorter with indirect adapter", "[indire { std::vector collection; collection.reserve(412); auto distribution = dist::shuffled{}; - distribution(std::back_inserter(collection), 412, -125.0); + distribution.call(std::back_inserter(collection), 412, -125); cppsort::indirect_adapter sorter; sorter(collection); @@ -61,7 +61,7 @@ TEMPLATE_TEST_CASE( "every bidirectional sorter with indirect_adapter", "[indire { std::list collection; auto distribution = dist::shuffled{}; - distribution(std::back_inserter(collection), 412, -125.0); + distribution.call(std::back_inserter(collection), 412, -125); cppsort::indirect_adapter sorter; sorter(collection); @@ -79,7 +79,7 @@ TEMPLATE_TEST_CASE( "every forward sorter with with indirect_adapter", "[indirec { std::forward_list collection; auto distribution = dist::shuffled{}; - distribution(std::front_inserter(collection), 412, -125.0); + distribution.call(std::front_inserter(collection), 412, -125); cppsort::indirect_adapter sorter; sorter(collection); diff --git a/testsuite/adapters/schwartz_adapter_every_sorter.cpp b/testsuite/adapters/schwartz_adapter_every_sorter.cpp index 22d4ae9f..2cbc951f 100644 --- a/testsuite/adapters/schwartz_adapter_every_sorter.cpp +++ b/testsuite/adapters/schwartz_adapter_every_sorter.cpp @@ -48,7 +48,7 @@ TEMPLATE_TEST_CASE( "every random-access sorter with Schwartzian transform adapt { std::vector> collection; auto distribution = dist::shuffled{}; - distribution(std::back_inserter(collection), 412, -125); + distribution.call(std::back_inserter(collection), 412, -125); cppsort::schwartz_adapter sorter; sorter(collection, &wrapper<>::value); @@ -69,7 +69,7 @@ TEMPLATE_TEST_CASE( "every bidirectional sorter with Schwartzian transform adapt { std::list> collection; auto distribution = dist::shuffled{}; - distribution(std::back_inserter(collection), 412, -125); + distribution.call(std::back_inserter(collection), 412, -125); cppsort::schwartz_adapter sorter; sorter(collection, &wrapper<>::value); @@ -87,7 +87,7 @@ TEMPLATE_TEST_CASE( "every forward sorter with Schwartzian transform adapter", " { std::forward_list> collection; auto distribution = dist::shuffled{}; - distribution(std::front_inserter(collection), 412, -125); + distribution.call(std::front_inserter(collection), 412, -125); cppsort::schwartz_adapter sorter; sorter(collection, &wrapper<>::value); @@ -100,7 +100,7 @@ TEST_CASE( "type-specific sorters with Schwartzian transform adapter", "[schwart auto distribution = dist::shuffled{}; std::vector> collection(412); - distribution(std::back_inserter(collection), 412, -125); + distribution.call(std::back_inserter(collection), 412, -125); std::vector> collection2(412); distribution(std::back_inserter(collection2), 412, -125); diff --git a/testsuite/adapters/schwartz_adapter_every_sorter_reversed.cpp b/testsuite/adapters/schwartz_adapter_every_sorter_reversed.cpp index b5a089d0..a6a66b5d 100644 --- a/testsuite/adapters/schwartz_adapter_every_sorter_reversed.cpp +++ b/testsuite/adapters/schwartz_adapter_every_sorter_reversed.cpp @@ -48,7 +48,7 @@ TEMPLATE_TEST_CASE( "every sorter with Schwartzian transform adapter and reverse { std::vector> collection; auto distribution = dist::shuffled{}; - distribution(std::back_inserter(collection), 412, -125); + distribution.call(std::back_inserter(collection), 412, -125); cppsort::schwartz_adapter sorter; sorter(collection.rbegin(), collection.rend(), &wrapper<>::value); @@ -62,7 +62,7 @@ TEST_CASE( "type-specific sorters with Schwartzian transform adapter and reverse auto distribution = dist::shuffled{}; std::vector> collection; - distribution(std::back_inserter(collection), 412, -125); + distribution.call(std::back_inserter(collection), 412, -125); std::vector> collection2; distribution(std::back_inserter(collection2), 412, -125); diff --git a/testsuite/adapters/verge_adapter_every_sorter.cpp b/testsuite/adapters/verge_adapter_every_sorter.cpp index ec3f59d7..a5776d7d 100644 --- a/testsuite/adapters/verge_adapter_every_sorter.cpp +++ b/testsuite/adapters/verge_adapter_every_sorter.cpp @@ -41,7 +41,7 @@ TEMPLATE_TEST_CASE( "every sorter with verge_adapter", "[verge_adapter]", { std::vector collection; collection.reserve(412); auto distribution = dist::shuffled{}; - distribution(std::back_inserter(collection), 412, -125.0); + distribution.call(std::back_inserter(collection), 412, -125); cppsort::verge_adapter sorter; sorter(collection); diff --git a/testsuite/every_sorter_move_only.cpp b/testsuite/every_sorter_move_only.cpp index e7e81434..597d96e2 100644 --- a/testsuite/every_sorter_move_only.cpp +++ b/testsuite/every_sorter_move_only.cpp @@ -42,7 +42,7 @@ TEMPLATE_TEST_CASE( "test every sorter with move-only types", "[sorters]", std::vector> collection; collection.reserve(491); auto distribution = dist::shuffled{}; - distribution(std::back_inserter(collection), 491, -125); + distribution.call(std::back_inserter(collection), 491, -125); TestType sorter; sorter(collection); diff --git a/testsuite/every_sorter_no_post_iterator.cpp b/testsuite/every_sorter_no_post_iterator.cpp index 958d725c..c1075b3d 100644 --- a/testsuite/every_sorter_no_post_iterator.cpp +++ b/testsuite/every_sorter_no_post_iterator.cpp @@ -61,7 +61,7 @@ TEMPLATE_TEST_CASE( "test type-specific sorters with no_post_iterator further", auto distribution = dist::shuffled{}; std::vector collection_float; - distribution(std::back_inserter(collection_float), 310, -56); + distribution.call(std::back_inserter(collection_float), 310, -56); // Iterators with no post-increment and no post-decrement auto first_float = make_no_post_iterator(collection_float.begin()); @@ -71,7 +71,7 @@ TEMPLATE_TEST_CASE( "test type-specific sorters with no_post_iterator further", CHECK( std::is_sorted(collection_float.begin(), collection_float.end()) ); std::vector collection_double; - distribution(std::back_inserter(collection_double), 310, -56); + distribution.call(std::back_inserter(collection_double), 310, -56); // Iterators with no post-increment and no post-decrement auto first_double = make_no_post_iterator(collection_double.begin()); diff --git a/testsuite/sorters/ska_sorter.cpp b/testsuite/sorters/ska_sorter.cpp index 4c75cb83..a01abbf6 100644 --- a/testsuite/sorters/ska_sorter.cpp +++ b/testsuite/sorters/ska_sorter.cpp @@ -57,7 +57,7 @@ TEST_CASE( "ska_sorter tests", "[ska_sorter]" ) SECTION( "sort with float iterable" ) { std::vector vec; - distribution(std::back_inserter(vec), 100'000); + distribution.call(std::back_inserter(vec), 100'000); cppsort::ska_sort(vec); CHECK( std::is_sorted(vec.begin(), vec.end()) ); } @@ -65,7 +65,7 @@ TEST_CASE( "ska_sorter tests", "[ska_sorter]" ) SECTION( "sort with double iterators" ) { std::vector vec; - distribution(std::back_inserter(vec), 100'000); + distribution.call(std::back_inserter(vec), 100'000); cppsort::ska_sort(vec.begin(), vec.end()); CHECK( std::is_sorted(vec.begin(), vec.end()) ); } diff --git a/testsuite/sorters/ska_sorter_projection.cpp b/testsuite/sorters/ska_sorter_projection.cpp index 00f14265..b9b5854b 100644 --- a/testsuite/sorters/ska_sorter_projection.cpp +++ b/testsuite/sorters/ska_sorter_projection.cpp @@ -23,7 +23,7 @@ TEST_CASE( "ska_sorter tests with projections", { std::vector> vec; for (int i = 0 ; i < 100'000 ; ++i) { - vec.emplace_back(i, i); + vec.emplace_back(i, float(i)); } std::shuffle(std::begin(vec), std::end(vec), engine); cppsort::ska_sort(vec, &std::pair::first); @@ -35,7 +35,7 @@ TEST_CASE( "ska_sorter tests with projections", { std::vector> vec; for (int i = 0 ; i < 100'000 ; ++i) { - vec.emplace_back(i, i); + vec.emplace_back(i, float(i)); } std::shuffle(std::begin(vec), std::end(vec), engine); cppsort::ska_sort(vec, &std::pair::first); @@ -47,7 +47,7 @@ TEST_CASE( "ska_sorter tests with projections", { std::vector> vec; for (int i = 0 ; i < 100'000 ; ++i) { - vec.emplace_back(i, i); + vec.emplace_back(i, float(i)); } std::shuffle(std::begin(vec), std::end(vec), engine); cppsort::ska_sort(vec, &std::pair::second); @@ -59,7 +59,7 @@ TEST_CASE( "ska_sorter tests with projections", { std::vector> vec; for (int i = 0 ; i < 100'000 ; ++i) { - vec.emplace_back(i, i); + vec.emplace_back(i, double(i)); } std::shuffle(std::begin(vec), std::end(vec), engine); cppsort::ska_sort(vec, &std::pair::second); diff --git a/testsuite/sorters/spread_sorter.cpp b/testsuite/sorters/spread_sorter.cpp index 130ca5a7..50974cbf 100644 --- a/testsuite/sorters/spread_sorter.cpp +++ b/testsuite/sorters/spread_sorter.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2020 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -37,7 +37,7 @@ TEST_CASE( "spread_sorter tests", "[spread_sorter]" ) SECTION( "sort with float iterable" ) { std::vector vec; - distribution(std::back_inserter(vec), 100'000); + distribution.call(std::back_inserter(vec), 100'000); cppsort::spread_sort(vec); CHECK( std::is_sorted(vec.begin(), vec.end()) ); } @@ -45,7 +45,7 @@ TEST_CASE( "spread_sorter tests", "[spread_sorter]" ) SECTION( "sort with double iterators" ) { std::vector vec; - distribution(std::back_inserter(vec), 100'000); + distribution.call(std::back_inserter(vec), 100'000); cppsort::spread_sort(vec.begin(), vec.end()); CHECK( std::is_sorted(vec.begin(), vec.end()) ); } diff --git a/testsuite/sorters/spread_sorter_projection.cpp b/testsuite/sorters/spread_sorter_projection.cpp index 5b181cdb..4d795ca9 100644 --- a/testsuite/sorters/spread_sorter_projection.cpp +++ b/testsuite/sorters/spread_sorter_projection.cpp @@ -23,7 +23,7 @@ TEST_CASE( "spread_sorter tests with projections", { std::vector> vec; for (int i = 0 ; i < 100'000 ; ++i) { - vec.emplace_back(i, i); + vec.emplace_back(i, float(i)); } std::shuffle(std::begin(vec), std::end(vec), engine); cppsort::spread_sort(vec, &std::pair::first); @@ -35,7 +35,7 @@ TEST_CASE( "spread_sorter tests with projections", { std::vector> vec; for (int i = 0 ; i < 100'000 ; ++i) { - vec.emplace_back(i, i); + vec.emplace_back(i, float(i)); } std::shuffle(std::begin(vec), std::end(vec), engine); cppsort::spread_sort(vec, &std::pair::first); @@ -47,7 +47,7 @@ TEST_CASE( "spread_sorter tests with projections", { std::vector> vec; for (int i = 0 ; i < 100'000 ; ++i) { - vec.emplace_back(i, i); + vec.emplace_back(i, float(i)); } std::shuffle(std::begin(vec), std::end(vec), engine); cppsort::spread_sort(vec, &std::pair::second); @@ -59,7 +59,7 @@ TEST_CASE( "spread_sorter tests with projections", { std::vector> vec; for (int i = 0 ; i < 100'000 ; ++i) { - vec.emplace_back(i, i); + vec.emplace_back(i, double(i)); } std::shuffle(std::begin(vec), std::end(vec), engine); cppsort::spread_sort(vec, &std::pair::second); diff --git a/testsuite/testing-tools/distributions.h b/testsuite/testing-tools/distributions.h index 173d3b7c..9c9c9475 100644 --- a/testsuite/testing-tools/distributions.h +++ b/testsuite/testing-tools/distributions.h @@ -38,6 +38,21 @@ namespace dist return Derived{}(out, size); }; } + + // Make it easier to specify explicit template parameters + template + auto call(OutputIterator out, long long int size) const + -> decltype(auto) + { + return static_cast(*this).template operator()(out, size); + } + + template + auto call(OutputIterator out, long long int size, long long int start) const + -> decltype(auto) + { + return static_cast(*this).template operator()(out, size, start); + } }; struct shuffled: @@ -71,12 +86,12 @@ namespace dist struct all_equal: distribution { - template + template auto operator()(OutputIterator out, long long int size) const -> void { for (long long int i = 0 ; i < size ; ++i) { - *out++ = 0; + *out++ = static_cast(0); } } }; @@ -84,12 +99,12 @@ namespace dist struct ascending: distribution { - template + template auto operator()(OutputIterator out, long long int size) const -> void { for (long long int i = 0 ; i < size ; ++i) { - *out++ = i; + *out++ = static_cast(i); } } }; @@ -97,12 +112,12 @@ namespace dist struct descending: distribution { - template + template auto operator()(OutputIterator out, long long int size) const -> void { while (size--) { - *out++ = size; + *out++ = static_cast(size); } } }; @@ -114,12 +129,12 @@ namespace dist // times the same integer value, used to test specific // algorithms against inputs with duplicate values - template + template auto operator()(OutputIterator out, long long int size) const -> void { for (long long int i = 0 ; i < size ; ++i) { - *out++ = i / 10; + *out++ = static_cast(i / 10); } } }; @@ -127,15 +142,15 @@ namespace dist struct pipe_organ: distribution { - template + template auto operator()(OutputIterator out, long long int size) const -> void { for (long long int i = 0 ; i < size / 2 ; ++i) { - *out++ = i; + *out++ = static_cast(i); } for (long long int i = size / 2 ; i < size ; ++i) { - *out++ = size - i; + *out++ = static_cast(size - i); } } }; @@ -143,15 +158,15 @@ namespace dist struct push_front: distribution { - template + template auto operator()(OutputIterator out, long long int size) const -> void { if (size > 0) { for (long long int i = 0 ; i < size - 1 ; ++i) { - *out++ = i; + *out++ = static_cast(i); } - *out = 0; + *out = static_cast(0); } } }; @@ -159,17 +174,17 @@ namespace dist struct push_middle: distribution { - template + template auto operator()(OutputIterator out, long long int size) const -> void { if (size > 0) { for (long long int i = 0 ; i < size ; ++i) { if (i != size / 2) { - *out++ = i; + *out++ = static_cast(i); } } - *out = size / 2; + *out = static_cast(size / 2); } } }; @@ -177,13 +192,13 @@ namespace dist struct ascending_sawtooth: distribution { - template + template auto operator()(OutputIterator out, long long int size) const -> void { - long long int limit = size / cppsort::detail::log2(size) * 0.9; + auto limit = static_cast(size / cppsort::detail::log2(size) * 0.9); for (long long int i = 0 ; i < size ; ++i) { - *out++ = i % limit; + *out++ = static_cast(i % limit); } } }; @@ -191,13 +206,13 @@ namespace dist struct descending_sawtooth: distribution { - template + template auto operator()(OutputIterator out, long long int size) const -> void { - long long int limit = size / cppsort::detail::log2(size) * 0.9; + auto limit = static_cast(size / cppsort::detail::log2(size) * 0.9); while (size--) { - *out++ = size % limit; + *out++ = static_cast(size % limit); } } }; @@ -205,12 +220,12 @@ namespace dist struct alternating: distribution { - template + template auto operator()(OutputIterator out, long long int size) const -> void { for (long long int i = 0 ; i < size ; ++i) { - *out++ = (i % 2) ? i : -i; + *out++ = static_cast((i % 2) ? i : -i); } } }; @@ -218,21 +233,21 @@ namespace dist struct descending_plateau: distribution { - template + template auto operator()(OutputIterator out, long long int size) const -> void { long long int i = size; while (i > 2 * size / 3) { - *out++ = i; + *out++ = static_cast(i); --i; } while (i > size / 3) { - *out++ = size / 2; + *out++ = static_cast(size / 2); --i; } while (i > 0) { - *out++ = i; + *out++ = static_cast(i); --i; } } @@ -245,20 +260,20 @@ namespace dist // by M. D. McIlroy, and is supposed to trick several quicksort // implementations with common pivot selection methods go quadratic - template + template auto operator()(OutputIterator out, long long int size) const -> void { long long int j = size / 2; for (long long int i = 1 ; i < j + 1 ; ++i) { if (i % 2 != 0) { - *out++ = i; + *out++ = static_cast(i); } else { - *out++ = j + i - 1; + *out++ = static_cast(j + i - 1); } } for (long long int i = 1 ; i < j + 1 ; ++i) { - *out++ = 2 * i; + *out++ = static_cast(2 * i); } } }; From aa8c4cededb1da83d0ff6113138b2e5a10c1928c Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 30 Nov 2021 19:19:03 +0100 Subject: [PATCH 74/79] More random facilities work in the test suite - Use xoshiro256** instead of mt19937_64 - Move random.h from the library to the test suite - Use the same two thread_local engines everywhere - Put thread_local engines in random.h (not in distributions) - Move thread_local engine definitions to random.cpp All those changes should hopefully simplify the handling of random numbers in the test suite, and possibly fix some of the -Winline warnings I constantly got with GCC, which I suspect were due either to the thread_local variables or to the size of the mt19937_64 engines. --- include/cpp-sort/detail/bitops.h | 11 ++ testsuite/CMakeLists.txt | 6 +- .../schwartz_adapter_every_sorter.cpp | 7 +- ...schwartz_adapter_every_sorter_reversed.cpp | 7 +- .../schwartz_adapter_fixed_sorters.cpp | 62 +++++----- testsuite/every_sorter_long_string.cpp | 7 +- testsuite/every_sorter_no_post_iterator.cpp | 5 +- testsuite/sorters/ska_sorter.cpp | 9 +- testsuite/sorters/ska_sorter_projection.cpp | 15 +-- testsuite/sorters/spread_sorter.cpp | 13 +- testsuite/sorters/spread_sorter_defaults.cpp | 19 ++- .../sorters/spread_sorter_projection.cpp | 17 ++- testsuite/testing-tools/distributions.h | 18 +-- testsuite/testing-tools/random.cpp | 25 ++++ .../testing-tools}/random.h | 112 ++++++++++++++++-- 15 files changed, 213 insertions(+), 120 deletions(-) create mode 100644 testsuite/testing-tools/random.cpp rename {include/cpp-sort/detail => testsuite/testing-tools}/random.h (51%) diff --git a/include/cpp-sort/detail/bitops.h b/include/cpp-sort/detail/bitops.h index 14c2714f..26681760 100644 --- a/include/cpp-sort/detail/bitops.h +++ b/include/cpp-sort/detail/bitops.h @@ -11,6 +11,7 @@ #include #include #include +#include "../detail/config.h" #include "../detail/type_traits.h" namespace cppsort @@ -116,6 +117,16 @@ namespace detail auto x = static_cast>(n); return x != 0 && (x & (x - 1)) == 0; } + + // Left bit rotation + template + constexpr auto rotl(Unsigned x, int s) + -> Unsigned + { + CPPSORT_ASSERT(s > 0); + constexpr auto n = std::numeric_limits::digits; + return (x << s) | (x >> (n - s)); + } }} #endif // CPPSORT_DETAIL_BITOPS_H_ diff --git a/testsuite/CMakeLists.txt b/testsuite/CMakeLists.txt index 33bd0ec8..da04bff0 100644 --- a/testsuite/CMakeLists.txt +++ b/testsuite/CMakeLists.txt @@ -103,8 +103,11 @@ endmacro() # Main tests add_executable(main-tests - # General tests + # Tooling main.cpp + testing-tools/random.cpp + + # General tests every_instantiated_sorter.cpp every_sorter.cpp every_sorter_internal_compare.cpp @@ -232,6 +235,7 @@ if (NOT "${CPPSORT_SANITIZE}" MATCHES "address|memory") # which isn't something we want for the main tests main.cpp testing-tools/new_delete.cpp + testing-tools/random.cpp heap_memory_exhaustion.cpp probes/every_probe_heap_memory_exhaustion.cpp ) diff --git a/testsuite/adapters/schwartz_adapter_every_sorter.cpp b/testsuite/adapters/schwartz_adapter_every_sorter.cpp index 2cbc951f..dcd266a8 100644 --- a/testsuite/adapters/schwartz_adapter_every_sorter.cpp +++ b/testsuite/adapters/schwartz_adapter_every_sorter.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include @@ -15,6 +14,7 @@ #include #include #include +#include #include // NOTE: this test used to use wrapper, but it was later @@ -109,8 +109,7 @@ TEST_CASE( "type-specific sorters with Schwartzian transform adapter", "[schwart for (int i = -125 ; i < 287 ; ++i) { collection3.emplace_back(std::to_string(i)); } - std::mt19937 engine(Catch::rngSeed()); - std::shuffle(collection3.begin(), collection3.end(), engine); + std::shuffle(collection3.begin(), collection3.end(), random::engine()); SECTION( "ska_sorter" ) { @@ -145,7 +144,7 @@ TEST_CASE( "type-specific sorters with Schwartzian transform adapter", "[schwart CHECK( helpers::is_sorted(collection3.begin(), collection3.end(), std::less<>{}, &wrapper::value) ); - std::shuffle(collection3.begin(), collection3.end(), engine); + std::shuffle(collection3.begin(), collection3.end(), random::engine()); sorter(collection3, std::greater<>{}, &wrapper::value); CHECK( helpers::is_sorted(collection3.begin(), collection3.end(), std::greater<>{}, &wrapper::value) ); diff --git a/testsuite/adapters/schwartz_adapter_every_sorter_reversed.cpp b/testsuite/adapters/schwartz_adapter_every_sorter_reversed.cpp index a6a66b5d..ef32f232 100644 --- a/testsuite/adapters/schwartz_adapter_every_sorter_reversed.cpp +++ b/testsuite/adapters/schwartz_adapter_every_sorter_reversed.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include @@ -14,6 +13,7 @@ #include #include #include +#include #include // NOTE: this test used to use wrapper, but it was later @@ -71,8 +71,7 @@ TEST_CASE( "type-specific sorters with Schwartzian transform adapter and reverse for (int i = -125 ; i < 287 ; ++i) { collection3.emplace_back(std::to_string(i)); } - std::mt19937 engine(Catch::rngSeed()); - std::shuffle(collection3.begin(), collection3.end(), engine); + std::shuffle(collection3.begin(), collection3.end(), random::engine()); SECTION( "ska_sorter" ) { @@ -107,7 +106,7 @@ TEST_CASE( "type-specific sorters with Schwartzian transform adapter and reverse CHECK( helpers::is_sorted(collection3.begin(), collection3.end(), std::greater<>{}, &wrapper::value) ); - std::shuffle(collection3.begin(), collection3.end(), engine); + std::shuffle(collection3.begin(), collection3.end(), random::engine()); sorter(collection3.rbegin(), collection3.rend(), std::greater<>{}, &wrapper::value); CHECK( helpers::is_sorted(collection3.begin(), collection3.end(), diff --git a/testsuite/adapters/schwartz_adapter_fixed_sorters.cpp b/testsuite/adapters/schwartz_adapter_fixed_sorters.cpp index b4c5e126..a4f1b40f 100644 --- a/testsuite/adapters/schwartz_adapter_fixed_sorters.cpp +++ b/testsuite/adapters/schwartz_adapter_fixed_sorters.cpp @@ -5,12 +5,12 @@ #include #include #include -#include #include #include #include #include #include +#include #include using wrapper = generic_wrapper; @@ -44,8 +44,6 @@ TEST_CASE( "Schwartzian transform adapter with fixed-size sorters", > >{}; - std::mt19937 engine(Catch::rngSeed()); - SECTION( "size 0" ) { std::array collection; @@ -73,27 +71,27 @@ TEST_CASE( "Schwartzian transform adapter with fixed-size sorters", std::array collection; helpers::iota(std::begin(collection), std::end(collection), -10.0, &wrapper::value); - std::shuffle(std::begin(collection), std::end(collection), engine); + std::shuffle(std::begin(collection), std::end(collection), random::engine()); low_comparisons_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); - std::shuffle(std::begin(collection), std::end(collection), engine); + std::shuffle(std::begin(collection), std::end(collection), random::engine()); low_moves_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); - std::shuffle(std::begin(collection), std::end(collection), engine); + std::shuffle(std::begin(collection), std::end(collection), random::engine()); merge_exchange_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); - std::shuffle(std::begin(collection), std::end(collection), engine); + std::shuffle(std::begin(collection), std::end(collection), random::engine()); odd_even_merge_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); - std::shuffle(std::begin(collection), std::end(collection), engine); + std::shuffle(std::begin(collection), std::end(collection), random::engine()); sorting_network_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); @@ -104,22 +102,22 @@ TEST_CASE( "Schwartzian transform adapter with fixed-size sorters", std::array collection; helpers::iota(std::begin(collection), std::end(collection), -10.0, &wrapper::value); - std::shuffle(std::begin(collection), std::end(collection), engine); + std::shuffle(std::begin(collection), std::end(collection), random::engine()); low_comparisons_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); - std::shuffle(std::begin(collection), std::end(collection), engine); + std::shuffle(std::begin(collection), std::end(collection), random::engine()); low_moves_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); - std::shuffle(std::begin(collection), std::end(collection), engine); + std::shuffle(std::begin(collection), std::end(collection), random::engine()); merge_exchange_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); - std::shuffle(std::begin(collection), std::end(collection), engine); + std::shuffle(std::begin(collection), std::end(collection), random::engine()); sorting_network_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); @@ -130,27 +128,27 @@ TEST_CASE( "Schwartzian transform adapter with fixed-size sorters", std::array collection; helpers::iota(std::begin(collection), std::end(collection), -10.0, &wrapper::value); - std::shuffle(std::begin(collection), std::end(collection), engine); + std::shuffle(std::begin(collection), std::end(collection), random::engine()); low_comparisons_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); - std::shuffle(std::begin(collection), std::end(collection), engine); + std::shuffle(std::begin(collection), std::end(collection), random::engine()); low_moves_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); - std::shuffle(std::begin(collection), std::end(collection), engine); + std::shuffle(std::begin(collection), std::end(collection), random::engine()); merge_exchange_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); - std::shuffle(std::begin(collection), std::end(collection), engine); + std::shuffle(std::begin(collection), std::end(collection), random::engine()); odd_even_merge_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); - std::shuffle(std::begin(collection), std::end(collection), engine); + std::shuffle(std::begin(collection), std::end(collection), random::engine()); sorting_network_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); @@ -161,22 +159,22 @@ TEST_CASE( "Schwartzian transform adapter with fixed-size sorters", std::array collection; helpers::iota(std::begin(collection), std::end(collection), -10.0, &wrapper::value); - std::shuffle(std::begin(collection), std::end(collection), engine); + std::shuffle(std::begin(collection), std::end(collection), random::engine()); low_comparisons_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); - std::shuffle(std::begin(collection), std::end(collection), engine); + std::shuffle(std::begin(collection), std::end(collection), random::engine()); low_moves_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); - std::shuffle(std::begin(collection), std::end(collection), engine); + std::shuffle(std::begin(collection), std::end(collection), random::engine()); merge_exchange_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); - std::shuffle(std::begin(collection), std::end(collection), engine); + std::shuffle(std::begin(collection), std::end(collection), random::engine()); sorting_network_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); @@ -187,22 +185,22 @@ TEST_CASE( "Schwartzian transform adapter with fixed-size sorters", std::array collection; helpers::iota(std::begin(collection), std::end(collection), -10.0, &wrapper::value); - std::shuffle(std::begin(collection), std::end(collection), engine); + std::shuffle(std::begin(collection), std::end(collection), random::engine()); low_comparisons_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); - std::shuffle(std::begin(collection), std::end(collection), engine); + std::shuffle(std::begin(collection), std::end(collection), random::engine()); low_moves_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); - std::shuffle(std::begin(collection), std::end(collection), engine); + std::shuffle(std::begin(collection), std::end(collection), random::engine()); merge_exchange_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); - std::shuffle(std::begin(collection), std::end(collection), engine); + std::shuffle(std::begin(collection), std::end(collection), random::engine()); sorting_network_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); @@ -213,17 +211,17 @@ TEST_CASE( "Schwartzian transform adapter with fixed-size sorters", std::array collection; helpers::iota(collection.begin(), collection.end(), -10.0, &wrapper::value); - std::shuffle(collection.begin(), collection.end(), engine); + std::shuffle(collection.begin(), collection.end(), random::engine()); low_moves_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(collection.begin(), collection.end(), std::less<>{}, &wrapper::value) ); - std::shuffle(collection.begin(), collection.end(), engine); + std::shuffle(collection.begin(), collection.end(), random::engine()); merge_exchange_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(collection.begin(), collection.end(), std::less<>{}, &wrapper::value) ); - std::shuffle(collection.begin(), collection.end(), engine); + std::shuffle(collection.begin(), collection.end(), random::engine()); sorting_network_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(collection.begin(), collection.end(), std::less<>{}, &wrapper::value) ); @@ -234,22 +232,22 @@ TEST_CASE( "Schwartzian transform adapter with fixed-size sorters", std::array collection; helpers::iota(collection.begin(), collection.end(), -10.0, &wrapper::value); - std::shuffle(collection.begin(), collection.end(), engine); + std::shuffle(collection.begin(), collection.end(), random::engine()); low_moves_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(collection.begin(), collection.end(), std::less<>{}, &wrapper::value) ); - std::shuffle(collection.begin(), collection.end(), engine); + std::shuffle(collection.begin(), collection.end(), random::engine()); merge_exchange_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(collection.begin(), collection.end(), std::less<>{}, &wrapper::value) ); - std::shuffle(collection.begin(), collection.end(), engine); + std::shuffle(collection.begin(), collection.end(), random::engine()); odd_even_merge_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(collection.begin(), collection.end(), std::less<>{}, &wrapper::value) ); - std::shuffle(collection.begin(), collection.end(), engine); + std::shuffle(collection.begin(), collection.end(), random::engine()); sorting_network_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(collection.begin(), collection.end(), std::less<>{}, &wrapper::value) ); diff --git a/testsuite/every_sorter_long_string.cpp b/testsuite/every_sorter_long_string.cpp index 0e234948..93b373d2 100644 --- a/testsuite/every_sorter_long_string.cpp +++ b/testsuite/every_sorter_long_string.cpp @@ -4,7 +4,6 @@ */ #include #include -#include #include #include #include @@ -13,6 +12,7 @@ #include #include #include +#include namespace { @@ -23,9 +23,6 @@ namespace auto operator()(OutputIterator out, long long int size, T start=T(0)) const -> void { - // Pseudo-random number generator - thread_local std::mt19937 engine(Catch::rngSeed()); - std::vector vec; vec.reserve(size); @@ -34,7 +31,7 @@ namespace auto s = std::to_string(i); vec.push_back(std::string(100 - s.size(), '0') + std::move(s)); } - std::shuffle(std::begin(vec), std::end(vec), engine); + std::shuffle(std::begin(vec), std::end(vec), random::engine()); std::move(std::begin(vec), std::end(vec), out); } }; diff --git a/testsuite/every_sorter_no_post_iterator.cpp b/testsuite/every_sorter_no_post_iterator.cpp index c1075b3d..87e83bfd 100644 --- a/testsuite/every_sorter_no_post_iterator.cpp +++ b/testsuite/every_sorter_no_post_iterator.cpp @@ -4,7 +4,6 @@ */ #include #include -#include #include #include #include @@ -12,6 +11,7 @@ #include #include #include +#include TEMPLATE_TEST_CASE( "test most sorters with no_post_iterator", "[sorters]", cppsort::cartesian_tree_sorter, @@ -84,8 +84,7 @@ TEMPLATE_TEST_CASE( "test type-specific sorters with no_post_iterator further", for (long i = 56 ; i < 366 ; ++i) { collection_str.emplace_back(std::to_string(i)); } - std::mt19937 engine(Catch::rngSeed()); - std::shuffle(collection_str.begin(), collection_str.end(), engine); + std::shuffle(collection_str.begin(), collection_str.end(), random::engine()); // Iterators with no post-increment and no post-decrement auto first_str = make_no_post_iterator(collection_str.begin()); diff --git a/testsuite/sorters/ska_sorter.cpp b/testsuite/sorters/ska_sorter.cpp index a01abbf6..db2d6126 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,6 +14,7 @@ #include #include #include +#include TEST_CASE( "ska_sorter tests", "[ska_sorter]" ) { @@ -77,14 +77,11 @@ TEST_CASE( "ska_sorter tests", "[ska_sorter]" ) vec.push_back(std::to_string(i)); } - // Pseudo-random number engine - std::mt19937_64 engine(Catch::rngSeed()); - - std::shuffle(vec.begin(), vec.end(), engine); + std::shuffle(vec.begin(), vec.end(), random::engine()); cppsort::ska_sort(vec); CHECK( std::is_sorted(vec.begin(), vec.end()) ); - std::shuffle(vec.begin(), vec.end(), engine); + std::shuffle(vec.begin(), vec.end(), random::engine()); cppsort::ska_sort(vec.begin(), vec.end()); CHECK( std::is_sorted(vec.begin(), vec.end()) ); } diff --git a/testsuite/sorters/ska_sorter_projection.cpp b/testsuite/sorters/ska_sorter_projection.cpp index b9b5854b..3269834e 100644 --- a/testsuite/sorters/ska_sorter_projection.cpp +++ b/testsuite/sorters/ska_sorter_projection.cpp @@ -5,27 +5,24 @@ #include #include #include -#include #include #include #include #include #include +#include #include TEST_CASE( "ska_sorter tests with projections", "[ska_sorter][projection]" ) { - // Pseudo-random number engine - std::mt19937_64 engine(Catch::rngSeed()); - SECTION( "sort with int iterable" ) { std::vector> vec; for (int i = 0 ; i < 100'000 ; ++i) { vec.emplace_back(i, float(i)); } - std::shuffle(std::begin(vec), std::end(vec), engine); + std::shuffle(std::begin(vec), std::end(vec), random::engine()); cppsort::ska_sort(vec, &std::pair::first); CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::less<>{}, &std::pair::second) ); @@ -37,7 +34,7 @@ TEST_CASE( "ska_sorter tests with projections", for (int i = 0 ; i < 100'000 ; ++i) { vec.emplace_back(i, float(i)); } - std::shuffle(std::begin(vec), std::end(vec), engine); + std::shuffle(std::begin(vec), std::end(vec), random::engine()); cppsort::ska_sort(vec, &std::pair::first); CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::less<>{}, &std::pair::second) ); @@ -49,7 +46,7 @@ TEST_CASE( "ska_sorter tests with projections", for (int i = 0 ; i < 100'000 ; ++i) { vec.emplace_back(i, float(i)); } - std::shuffle(std::begin(vec), std::end(vec), engine); + std::shuffle(std::begin(vec), std::end(vec), random::engine()); cppsort::ska_sort(vec, &std::pair::second); CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::less<>{}, &std::pair::first) ); @@ -61,7 +58,7 @@ TEST_CASE( "ska_sorter tests with projections", for (int i = 0 ; i < 100'000 ; ++i) { vec.emplace_back(i, double(i)); } - std::shuffle(std::begin(vec), std::end(vec), engine); + std::shuffle(std::begin(vec), std::end(vec), random::engine()); cppsort::ska_sort(vec, &std::pair::second); CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::less<>{}, &std::pair::first) ); @@ -75,7 +72,7 @@ TEST_CASE( "ska_sorter tests with projections", for (int i = 0 ; i < 100'000 ; ++i) { vec.emplace_back(std::to_string(i)); } - std::shuffle(std::begin(vec), std::end(vec), engine); + std::shuffle(std::begin(vec), std::end(vec), random::engine()); cppsort::ska_sort(vec, &wrapper::value); CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::less<>{}, &wrapper::value) ); diff --git a/testsuite/sorters/spread_sorter.cpp b/testsuite/sorters/spread_sorter.cpp index 50974cbf..17d21f0e 100644 --- a/testsuite/sorters/spread_sorter.cpp +++ b/testsuite/sorters/spread_sorter.cpp @@ -4,18 +4,15 @@ */ #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" ) @@ -57,11 +54,11 @@ TEST_CASE( "spread_sorter tests", "[spread_sorter]" ) vec.push_back(std::to_string(i)); } - std::shuffle(vec.begin(), vec.end(), engine); + std::shuffle(vec.begin(), vec.end(), random::engine()); cppsort::spread_sort(vec); CHECK( std::is_sorted(vec.begin(), vec.end()) ); - std::shuffle(vec.begin(), vec.end(), engine); + std::shuffle(vec.begin(), vec.end(), random::engine()); cppsort::spread_sort(vec.begin(), vec.end()); CHECK( std::is_sorted(vec.begin(), vec.end()) ); } @@ -73,11 +70,11 @@ TEST_CASE( "spread_sorter tests", "[spread_sorter]" ) vec.push_back(std::to_string(i)); } - std::shuffle(vec.begin(), vec.end(), engine); + std::shuffle(vec.begin(), vec.end(), random::engine()); cppsort::spread_sort(vec, std::greater<>{}); CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); - std::shuffle(vec.begin(), vec.end(), engine); + std::shuffle(vec.begin(), vec.end(), random::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/spread_sorter_defaults.cpp b/testsuite/sorters/spread_sorter_defaults.cpp index 4675a931..5762e961 100644 --- a/testsuite/sorters/spread_sorter_defaults.cpp +++ b/testsuite/sorters/spread_sorter_defaults.cpp @@ -1,16 +1,16 @@ /* - * Copyright (c) 2015-2020 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #include #include #include #include -#include #include #include #include #include +#include TEST_CASE( "spread_sorter generate overloads", "[spread_sorter][sorter_facade]" ) @@ -19,19 +19,16 @@ TEST_CASE( "spread_sorter generate overloads", // to make sure that sorter_facade generates the expected // operator() overloads for non-comparison sorters - // Pseudo-random number engine - std::mt19937_64 engine(Catch::rngSeed()); - SECTION( "default operator() with std::less<>" ) { std::vector vec(100'000); std::iota(std::begin(vec), std::end(vec), 0); - std::shuffle(std::begin(vec), std::end(vec), engine); + std::shuffle(std::begin(vec), std::end(vec), random::engine()); cppsort::spread_sort(vec, std::less<>{}); CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::less<>{}) ); - std::shuffle(std::begin(vec), std::end(vec), engine); + std::shuffle(std::begin(vec), std::end(vec), random::engine()); cppsort::spread_sort(std::begin(vec), std::end(vec), std::less<>{}); CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::less<>{}) ); } @@ -41,11 +38,11 @@ TEST_CASE( "spread_sorter generate overloads", std::vector vec(100'000); std::iota(std::begin(vec), std::end(vec), 0); - std::shuffle(std::begin(vec), std::end(vec), engine); + std::shuffle(std::begin(vec), std::end(vec), random::engine()); cppsort::spread_sort(vec, cppsort::utility::identity{}); CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); - std::shuffle(std::begin(vec), std::end(vec), engine); + std::shuffle(std::begin(vec), std::end(vec), random::engine()); cppsort::spread_sort(std::begin(vec), std::end(vec), cppsort::utility::identity{}); CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); } @@ -55,11 +52,11 @@ TEST_CASE( "spread_sorter generate overloads", std::vector vec(100'000); std::iota(std::begin(vec), std::end(vec), 0); - std::shuffle(std::begin(vec), std::end(vec), engine); + std::shuffle(std::begin(vec), std::end(vec), random::engine()); cppsort::spread_sort(vec, std::less<>{}, cppsort::utility::identity{}); CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); - std::shuffle(std::begin(vec), std::end(vec), engine); + std::shuffle(std::begin(vec), std::end(vec), random::engine()); cppsort::spread_sort(std::begin(vec), std::end(vec), std::less<>{}, cppsort::utility::identity{}); CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); } diff --git a/testsuite/sorters/spread_sorter_projection.cpp b/testsuite/sorters/spread_sorter_projection.cpp index 4d795ca9..8f8477f6 100644 --- a/testsuite/sorters/spread_sorter_projection.cpp +++ b/testsuite/sorters/spread_sorter_projection.cpp @@ -5,27 +5,24 @@ #include #include #include -#include #include #include #include #include #include +#include #include TEST_CASE( "spread_sorter tests with projections", "[spread_sorter][projection]" ) { - // Pseudo-random number engine - std::mt19937_64 engine(Catch::rngSeed()); - SECTION( "sort with int iterable" ) { std::vector> vec; for (int i = 0 ; i < 100'000 ; ++i) { vec.emplace_back(i, float(i)); } - std::shuffle(std::begin(vec), std::end(vec), engine); + std::shuffle(std::begin(vec), std::end(vec), random::engine()); cppsort::spread_sort(vec, &std::pair::first); CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::less<>{}, &std::pair::second) ); @@ -37,7 +34,7 @@ TEST_CASE( "spread_sorter tests with projections", for (int i = 0 ; i < 100'000 ; ++i) { vec.emplace_back(i, float(i)); } - std::shuffle(std::begin(vec), std::end(vec), engine); + std::shuffle(std::begin(vec), std::end(vec), random::engine()); cppsort::spread_sort(vec, &std::pair::first); CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::less<>{}, &std::pair::second) ); @@ -49,7 +46,7 @@ TEST_CASE( "spread_sorter tests with projections", for (int i = 0 ; i < 100'000 ; ++i) { vec.emplace_back(i, float(i)); } - std::shuffle(std::begin(vec), std::end(vec), engine); + std::shuffle(std::begin(vec), std::end(vec), random::engine()); cppsort::spread_sort(vec, &std::pair::second); CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::less<>{}, &std::pair::first) ); @@ -61,7 +58,7 @@ TEST_CASE( "spread_sorter tests with projections", for (int i = 0 ; i < 100'000 ; ++i) { vec.emplace_back(i, double(i)); } - std::shuffle(std::begin(vec), std::end(vec), engine); + std::shuffle(std::begin(vec), std::end(vec), random::engine()); cppsort::spread_sort(vec, &std::pair::second); CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::less<>{}, &std::pair::first) ); @@ -75,7 +72,7 @@ TEST_CASE( "spread_sorter tests with projections", for (int i = 0 ; i < 100'000 ; ++i) { vec.emplace_back(std::to_string(i)); } - std::shuffle(std::begin(vec), std::end(vec), engine); + std::shuffle(std::begin(vec), std::end(vec), random::engine()); cppsort::spread_sort(vec, &wrapper::value); CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::less<>{}, &wrapper::value) ); @@ -89,7 +86,7 @@ TEST_CASE( "spread_sorter tests with projections", for (int i = 0 ; i < 100'000 ; ++i) { vec.emplace_back(std::to_string(i)); } - std::shuffle(std::begin(vec), std::end(vec), engine); + std::shuffle(std::begin(vec), std::end(vec), random::engine()); cppsort::spread_sort(vec, std::greater<>{}, &wrapper::value); CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}, &wrapper::value) ); diff --git a/testsuite/testing-tools/distributions.h b/testsuite/testing-tools/distributions.h index 9c9c9475..96fd6d64 100644 --- a/testsuite/testing-tools/distributions.h +++ b/testsuite/testing-tools/distributions.h @@ -8,23 +8,11 @@ //////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// -#include -#include #include -#include +#include "random.h" namespace dist { - inline auto gen() - -> cppsort::detail::rand_bit_generator& - { - // Utility allowing to fetch random bits from a URBG one by one - thread_local cppsort::detail::rand_bit_generator res{ - std::mt19937_64(Catch::rngSeed()) - }; - return res; - } - template struct distribution { @@ -62,7 +50,7 @@ namespace dist auto operator()(OutputIterator out, long long int size, long long int start=0ll) const -> void { - cppsort::detail::fill_with_shuffle(out, size, start, gen()); + random::fill_with_shuffle(out, size, start, random::bit_gen()); } }; @@ -79,7 +67,7 @@ namespace dist auto operator()(OutputIterator out, long long int size) const -> void { - cppsort::detail::fill_with_shuffle(out, size, 0, gen(), &mod_16); + random::fill_with_shuffle(out, size, 0, random::bit_gen(), &mod_16); } }; diff --git a/testsuite/testing-tools/random.cpp b/testsuite/testing-tools/random.cpp new file mode 100644 index 00000000..5d51de01 --- /dev/null +++ b/testsuite/testing-tools/random.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2021 Morwenn + * SPDX-License-Identifier: MIT + */ +#include +#include "random.h" + +namespace random +{ + auto engine() + -> xoshiro256ss& + { + thread_local xoshiro256ss res(Catch::rngSeed()); + return res; + } + + auto bit_gen() + -> rand_bit_generator& + { + thread_local rand_bit_generator res{ + xoshiro256ss(Catch::rngSeed()) + }; + return res; + } +} diff --git a/include/cpp-sort/detail/random.h b/testsuite/testing-tools/random.h similarity index 51% rename from include/cpp-sort/detail/random.h rename to testsuite/testing-tools/random.h index 3fb2d04d..369b1c64 100644 --- a/include/cpp-sort/detail/random.h +++ b/testsuite/testing-tools/random.h @@ -2,23 +2,100 @@ * Copyright (c) 2021 Morwenn * SPDX-License-Identifier: MIT */ -#ifndef CPPSORT_DETAIL_RANDOM_H_ -#define CPPSORT_DETAIL_RANDOM_H_ +#ifndef CPPSORT_TESTSUITE_RANDOM_H_ +#define CPPSORT_TESTSUITE_RANDOM_H_ //////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// #include -#include +#include #include +#include #include #include -#include "bitops.h" -namespace cppsort -{ -namespace detail +namespace random { + //////////////////////////////////////////////////////////// + // xoshiro256** + // https://prng.di.unimi.it/xoshiro256starstar.c + + struct xoshiro256ss + { + public: + + using result_type = std::uint64_t; + + //////////////////////////////////////////////////////////// + // Construction + + constexpr xoshiro256ss(): + xoshiro256ss(0) + {} + + constexpr explicit xoshiro256ss(result_type seed): + s{splitmix64(seed), + splitmix64(seed), + splitmix64(seed), + splitmix64(seed)} + { + // From the original link: "If you have a 64-bit seed, we + // suggest to seed a splitmix64 generator and use its + // output to fill s." + } + + //////////////////////////////////////////////////////////// + // Generation + + constexpr auto operator()() + -> result_type + { + const result_type result = cppsort::detail::rotl(s[1] * 5, 7) * 9; + const result_type t = s[1] << 17; + + s[2] ^= s[0]; + s[3] ^= s[1]; + s[1] ^= s[2]; + s[0] ^= s[3]; + s[2] ^= t; + s[3] = cppsort::detail::rotl(s[3], 45); + + return result; + } + + //////////////////////////////////////////////////////////// + // Characteristics + + static constexpr std::size_t word_size = std::numeric_limits::digits; + + static constexpr auto min() + -> result_type + { + return 0; + } + + static constexpr auto max() + -> result_type + { + return static_cast(-1); + } + + private: + + // State of the PRNG + result_type s[4]; + + static constexpr auto splitmix64(std::uint64_t& x) + -> std::uint64_t + { + std::uint64_t z = (x += 0x9e3779b97f4a7c15ull); + z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9ull; + z = (z ^ (z >> 27)) * 0x94d049bb133111ebull; + return z ^ (z >> 31); + } + }; + //////////////////////////////////////////////////////////// // rand_bit_generator // @@ -99,20 +176,20 @@ namespace detail typename T=long long int, typename OutputIterator, typename URBG, - typename Projection = utility::identity + typename Projection = cppsort::utility::identity > auto fill_with_shuffle(OutputIterator out, long long int size, long long int start, URBG& engine, Projection projection={}) -> void { assert(size >= 4); - auto&& proj = utility::as_function(projection); + auto&& proj = cppsort::utility::as_function(projection); // Generate a shuffle of all the integers in the range [start, start + size) // with a linear congruential generator // https://stackoverflow.com/a/44821946/1364752 - long long int m = hyperceil(size); + long long int m = cppsort::detail::hyperceil(size); auto a = randint(1ll, (m >> 2) - 1, engine) * 4 + 1; auto c = randint(3ll, m, engine) | 1; @@ -125,6 +202,17 @@ namespace detail ++out; } } -}} -#endif // CPPSORT_DETAIL_RANDOM_H_ + //////////////////////////////////////////////////////////// + // Thread-local "Meyers singletons" + + // Pseudo-random number generator engine + auto engine() + -> xoshiro256ss&; + + // Utility allowing to fetch random bits from a URBG one by one + auto bit_gen() + -> rand_bit_generator&; +} + +#endif // CPPSORT_TESTSUITE_RANDOM_H_ From dae3c74da457d2921675669d6ba03a8a53b4c30c Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 30 Nov 2021 23:01:45 +0100 Subject: [PATCH 75/79] Credit sources of random number algorithms --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index c5a646cc..65bd6370 100644 --- a/README.md +++ b/README.md @@ -218,6 +218,11 @@ discussion](https://stackoverflow.com/q/2786899/1364752) on StackOverflow and ar backed by the article [*Applying Sorting Networks to Synthesize Optimized Sorting Libraries*](https://arxiv.org/abs/1505.01962). +* The test suite reimplements random number algorithms originally found in the following places: + - [xoshiro256\*\*](https://prng.di.unimi.it/) + - [*Optimal Discrete Uniform Generation from Coin Flips, and Applications*](https://arxiv.org/abs/1304.1916) + - [*All numbers in a given range but random order*](https://stackoverflow.com/a/44821946/1364752) + * The LaTeX scripts used to draw the sorting networks are modified versions of kaayy's [`sortingnetwork.tex`](https://github.com/kaayy/kaayy-s-code-sinppets), slightly adapted to be 0-based and draw the network from top to bottom. From ecbfc6795ffb727bb4188497362e09a2da7398ee Mon Sep 17 00:00:00 2001 From: Morwenn Date: Tue, 30 Nov 2021 23:07:42 +0100 Subject: [PATCH 76/79] Give POSIX the middle finger --- .../schwartz_adapter_every_sorter.cpp | 4 +- ...schwartz_adapter_every_sorter_reversed.cpp | 4 +- .../schwartz_adapter_fixed_sorters.cpp | 58 +++++++++---------- testsuite/every_sorter_long_string.cpp | 2 +- testsuite/every_sorter_no_post_iterator.cpp | 2 +- testsuite/sorters/ska_sorter.cpp | 4 +- testsuite/sorters/ska_sorter_projection.cpp | 10 ++-- testsuite/sorters/spread_sorter.cpp | 8 +-- testsuite/sorters/spread_sorter_defaults.cpp | 12 ++-- .../sorters/spread_sorter_projection.cpp | 12 ++-- testsuite/testing-tools/distributions.h | 4 +- testsuite/testing-tools/random.cpp | 2 +- testsuite/testing-tools/random.h | 2 +- 13 files changed, 62 insertions(+), 62 deletions(-) diff --git a/testsuite/adapters/schwartz_adapter_every_sorter.cpp b/testsuite/adapters/schwartz_adapter_every_sorter.cpp index dcd266a8..8ec303f2 100644 --- a/testsuite/adapters/schwartz_adapter_every_sorter.cpp +++ b/testsuite/adapters/schwartz_adapter_every_sorter.cpp @@ -109,7 +109,7 @@ TEST_CASE( "type-specific sorters with Schwartzian transform adapter", "[schwart for (int i = -125 ; i < 287 ; ++i) { collection3.emplace_back(std::to_string(i)); } - std::shuffle(collection3.begin(), collection3.end(), random::engine()); + std::shuffle(collection3.begin(), collection3.end(), hasard::engine()); SECTION( "ska_sorter" ) { @@ -144,7 +144,7 @@ TEST_CASE( "type-specific sorters with Schwartzian transform adapter", "[schwart CHECK( helpers::is_sorted(collection3.begin(), collection3.end(), std::less<>{}, &wrapper::value) ); - std::shuffle(collection3.begin(), collection3.end(), random::engine()); + std::shuffle(collection3.begin(), collection3.end(), hasard::engine()); sorter(collection3, std::greater<>{}, &wrapper::value); CHECK( helpers::is_sorted(collection3.begin(), collection3.end(), std::greater<>{}, &wrapper::value) ); diff --git a/testsuite/adapters/schwartz_adapter_every_sorter_reversed.cpp b/testsuite/adapters/schwartz_adapter_every_sorter_reversed.cpp index ef32f232..00ceeef4 100644 --- a/testsuite/adapters/schwartz_adapter_every_sorter_reversed.cpp +++ b/testsuite/adapters/schwartz_adapter_every_sorter_reversed.cpp @@ -71,7 +71,7 @@ TEST_CASE( "type-specific sorters with Schwartzian transform adapter and reverse for (int i = -125 ; i < 287 ; ++i) { collection3.emplace_back(std::to_string(i)); } - std::shuffle(collection3.begin(), collection3.end(), random::engine()); + std::shuffle(collection3.begin(), collection3.end(), hasard::engine()); SECTION( "ska_sorter" ) { @@ -106,7 +106,7 @@ TEST_CASE( "type-specific sorters with Schwartzian transform adapter and reverse CHECK( helpers::is_sorted(collection3.begin(), collection3.end(), std::greater<>{}, &wrapper::value) ); - std::shuffle(collection3.begin(), collection3.end(), random::engine()); + std::shuffle(collection3.begin(), collection3.end(), hasard::engine()); sorter(collection3.rbegin(), collection3.rend(), std::greater<>{}, &wrapper::value); CHECK( helpers::is_sorted(collection3.begin(), collection3.end(), diff --git a/testsuite/adapters/schwartz_adapter_fixed_sorters.cpp b/testsuite/adapters/schwartz_adapter_fixed_sorters.cpp index a4f1b40f..1b14f498 100644 --- a/testsuite/adapters/schwartz_adapter_fixed_sorters.cpp +++ b/testsuite/adapters/schwartz_adapter_fixed_sorters.cpp @@ -71,27 +71,27 @@ TEST_CASE( "Schwartzian transform adapter with fixed-size sorters", std::array collection; helpers::iota(std::begin(collection), std::end(collection), -10.0, &wrapper::value); - std::shuffle(std::begin(collection), std::end(collection), random::engine()); + std::shuffle(std::begin(collection), std::end(collection), hasard::engine()); low_comparisons_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); - std::shuffle(std::begin(collection), std::end(collection), random::engine()); + std::shuffle(std::begin(collection), std::end(collection), hasard::engine()); low_moves_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); - std::shuffle(std::begin(collection), std::end(collection), random::engine()); + std::shuffle(std::begin(collection), std::end(collection), hasard::engine()); merge_exchange_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); - std::shuffle(std::begin(collection), std::end(collection), random::engine()); + std::shuffle(std::begin(collection), std::end(collection), hasard::engine()); odd_even_merge_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); - std::shuffle(std::begin(collection), std::end(collection), random::engine()); + std::shuffle(std::begin(collection), std::end(collection), hasard::engine()); sorting_network_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); @@ -102,22 +102,22 @@ TEST_CASE( "Schwartzian transform adapter with fixed-size sorters", std::array collection; helpers::iota(std::begin(collection), std::end(collection), -10.0, &wrapper::value); - std::shuffle(std::begin(collection), std::end(collection), random::engine()); + std::shuffle(std::begin(collection), std::end(collection), hasard::engine()); low_comparisons_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); - std::shuffle(std::begin(collection), std::end(collection), random::engine()); + std::shuffle(std::begin(collection), std::end(collection), hasard::engine()); low_moves_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); - std::shuffle(std::begin(collection), std::end(collection), random::engine()); + std::shuffle(std::begin(collection), std::end(collection), hasard::engine()); merge_exchange_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); - std::shuffle(std::begin(collection), std::end(collection), random::engine()); + std::shuffle(std::begin(collection), std::end(collection), hasard::engine()); sorting_network_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); @@ -128,27 +128,27 @@ TEST_CASE( "Schwartzian transform adapter with fixed-size sorters", std::array collection; helpers::iota(std::begin(collection), std::end(collection), -10.0, &wrapper::value); - std::shuffle(std::begin(collection), std::end(collection), random::engine()); + std::shuffle(std::begin(collection), std::end(collection), hasard::engine()); low_comparisons_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); - std::shuffle(std::begin(collection), std::end(collection), random::engine()); + std::shuffle(std::begin(collection), std::end(collection), hasard::engine()); low_moves_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); - std::shuffle(std::begin(collection), std::end(collection), random::engine()); + std::shuffle(std::begin(collection), std::end(collection), hasard::engine()); merge_exchange_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); - std::shuffle(std::begin(collection), std::end(collection), random::engine()); + std::shuffle(std::begin(collection), std::end(collection), hasard::engine()); odd_even_merge_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); - std::shuffle(std::begin(collection), std::end(collection), random::engine()); + std::shuffle(std::begin(collection), std::end(collection), hasard::engine()); sorting_network_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); @@ -159,22 +159,22 @@ TEST_CASE( "Schwartzian transform adapter with fixed-size sorters", std::array collection; helpers::iota(std::begin(collection), std::end(collection), -10.0, &wrapper::value); - std::shuffle(std::begin(collection), std::end(collection), random::engine()); + std::shuffle(std::begin(collection), std::end(collection), hasard::engine()); low_comparisons_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); - std::shuffle(std::begin(collection), std::end(collection), random::engine()); + std::shuffle(std::begin(collection), std::end(collection), hasard::engine()); low_moves_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); - std::shuffle(std::begin(collection), std::end(collection), random::engine()); + std::shuffle(std::begin(collection), std::end(collection), hasard::engine()); merge_exchange_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); - std::shuffle(std::begin(collection), std::end(collection), random::engine()); + std::shuffle(std::begin(collection), std::end(collection), hasard::engine()); sorting_network_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); @@ -185,22 +185,22 @@ TEST_CASE( "Schwartzian transform adapter with fixed-size sorters", std::array collection; helpers::iota(std::begin(collection), std::end(collection), -10.0, &wrapper::value); - std::shuffle(std::begin(collection), std::end(collection), random::engine()); + std::shuffle(std::begin(collection), std::end(collection), hasard::engine()); low_comparisons_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); - std::shuffle(std::begin(collection), std::end(collection), random::engine()); + std::shuffle(std::begin(collection), std::end(collection), hasard::engine()); low_moves_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); - std::shuffle(std::begin(collection), std::end(collection), random::engine()); + std::shuffle(std::begin(collection), std::end(collection), hasard::engine()); merge_exchange_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); - std::shuffle(std::begin(collection), std::end(collection), random::engine()); + std::shuffle(std::begin(collection), std::end(collection), hasard::engine()); sorting_network_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(std::begin(collection), std::end(collection), std::less<>{}, &wrapper::value) ); @@ -211,17 +211,17 @@ TEST_CASE( "Schwartzian transform adapter with fixed-size sorters", std::array collection; helpers::iota(collection.begin(), collection.end(), -10.0, &wrapper::value); - std::shuffle(collection.begin(), collection.end(), random::engine()); + std::shuffle(collection.begin(), collection.end(), hasard::engine()); low_moves_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(collection.begin(), collection.end(), std::less<>{}, &wrapper::value) ); - std::shuffle(collection.begin(), collection.end(), random::engine()); + std::shuffle(collection.begin(), collection.end(), hasard::engine()); merge_exchange_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(collection.begin(), collection.end(), std::less<>{}, &wrapper::value) ); - std::shuffle(collection.begin(), collection.end(), random::engine()); + std::shuffle(collection.begin(), collection.end(), hasard::engine()); sorting_network_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(collection.begin(), collection.end(), std::less<>{}, &wrapper::value) ); @@ -232,22 +232,22 @@ TEST_CASE( "Schwartzian transform adapter with fixed-size sorters", std::array collection; helpers::iota(collection.begin(), collection.end(), -10.0, &wrapper::value); - std::shuffle(collection.begin(), collection.end(), random::engine()); + std::shuffle(collection.begin(), collection.end(), hasard::engine()); low_moves_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(collection.begin(), collection.end(), std::less<>{}, &wrapper::value) ); - std::shuffle(collection.begin(), collection.end(), random::engine()); + std::shuffle(collection.begin(), collection.end(), hasard::engine()); merge_exchange_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(collection.begin(), collection.end(), std::less<>{}, &wrapper::value) ); - std::shuffle(collection.begin(), collection.end(), random::engine()); + std::shuffle(collection.begin(), collection.end(), hasard::engine()); odd_even_merge_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(collection.begin(), collection.end(), std::less<>{}, &wrapper::value) ); - std::shuffle(collection.begin(), collection.end(), random::engine()); + std::shuffle(collection.begin(), collection.end(), hasard::engine()); sorting_network_sort(collection, &wrapper::value); CHECK( helpers::is_sorted(collection.begin(), collection.end(), std::less<>{}, &wrapper::value) ); diff --git a/testsuite/every_sorter_long_string.cpp b/testsuite/every_sorter_long_string.cpp index 93b373d2..9bfd2e78 100644 --- a/testsuite/every_sorter_long_string.cpp +++ b/testsuite/every_sorter_long_string.cpp @@ -31,7 +31,7 @@ namespace auto s = std::to_string(i); vec.push_back(std::string(100 - s.size(), '0') + std::move(s)); } - std::shuffle(std::begin(vec), std::end(vec), random::engine()); + std::shuffle(std::begin(vec), std::end(vec), hasard::engine()); std::move(std::begin(vec), std::end(vec), out); } }; diff --git a/testsuite/every_sorter_no_post_iterator.cpp b/testsuite/every_sorter_no_post_iterator.cpp index 87e83bfd..41231f9a 100644 --- a/testsuite/every_sorter_no_post_iterator.cpp +++ b/testsuite/every_sorter_no_post_iterator.cpp @@ -84,7 +84,7 @@ TEMPLATE_TEST_CASE( "test type-specific sorters with no_post_iterator further", for (long i = 56 ; i < 366 ; ++i) { collection_str.emplace_back(std::to_string(i)); } - std::shuffle(collection_str.begin(), collection_str.end(), random::engine()); + std::shuffle(collection_str.begin(), collection_str.end(), hasard::engine()); // Iterators with no post-increment and no post-decrement auto first_str = make_no_post_iterator(collection_str.begin()); diff --git a/testsuite/sorters/ska_sorter.cpp b/testsuite/sorters/ska_sorter.cpp index db2d6126..c5b18062 100644 --- a/testsuite/sorters/ska_sorter.cpp +++ b/testsuite/sorters/ska_sorter.cpp @@ -77,11 +77,11 @@ TEST_CASE( "ska_sorter tests", "[ska_sorter]" ) vec.push_back(std::to_string(i)); } - std::shuffle(vec.begin(), vec.end(), random::engine()); + std::shuffle(vec.begin(), vec.end(), hasard::engine()); cppsort::ska_sort(vec); CHECK( std::is_sorted(vec.begin(), vec.end()) ); - std::shuffle(vec.begin(), vec.end(), random::engine()); + std::shuffle(vec.begin(), vec.end(), hasard::engine()); cppsort::ska_sort(vec.begin(), vec.end()); CHECK( std::is_sorted(vec.begin(), vec.end()) ); } diff --git a/testsuite/sorters/ska_sorter_projection.cpp b/testsuite/sorters/ska_sorter_projection.cpp index 3269834e..371eeb6c 100644 --- a/testsuite/sorters/ska_sorter_projection.cpp +++ b/testsuite/sorters/ska_sorter_projection.cpp @@ -22,7 +22,7 @@ TEST_CASE( "ska_sorter tests with projections", for (int i = 0 ; i < 100'000 ; ++i) { vec.emplace_back(i, float(i)); } - std::shuffle(std::begin(vec), std::end(vec), random::engine()); + std::shuffle(std::begin(vec), std::end(vec), hasard::engine()); cppsort::ska_sort(vec, &std::pair::first); CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::less<>{}, &std::pair::second) ); @@ -34,7 +34,7 @@ TEST_CASE( "ska_sorter tests with projections", for (int i = 0 ; i < 100'000 ; ++i) { vec.emplace_back(i, float(i)); } - std::shuffle(std::begin(vec), std::end(vec), random::engine()); + std::shuffle(std::begin(vec), std::end(vec), hasard::engine()); cppsort::ska_sort(vec, &std::pair::first); CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::less<>{}, &std::pair::second) ); @@ -46,7 +46,7 @@ TEST_CASE( "ska_sorter tests with projections", for (int i = 0 ; i < 100'000 ; ++i) { vec.emplace_back(i, float(i)); } - std::shuffle(std::begin(vec), std::end(vec), random::engine()); + std::shuffle(std::begin(vec), std::end(vec), hasard::engine()); cppsort::ska_sort(vec, &std::pair::second); CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::less<>{}, &std::pair::first) ); @@ -58,7 +58,7 @@ TEST_CASE( "ska_sorter tests with projections", for (int i = 0 ; i < 100'000 ; ++i) { vec.emplace_back(i, double(i)); } - std::shuffle(std::begin(vec), std::end(vec), random::engine()); + std::shuffle(std::begin(vec), std::end(vec), hasard::engine()); cppsort::ska_sort(vec, &std::pair::second); CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::less<>{}, &std::pair::first) ); @@ -72,7 +72,7 @@ TEST_CASE( "ska_sorter tests with projections", for (int i = 0 ; i < 100'000 ; ++i) { vec.emplace_back(std::to_string(i)); } - std::shuffle(std::begin(vec), std::end(vec), random::engine()); + std::shuffle(std::begin(vec), std::end(vec), hasard::engine()); cppsort::ska_sort(vec, &wrapper::value); CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::less<>{}, &wrapper::value) ); diff --git a/testsuite/sorters/spread_sorter.cpp b/testsuite/sorters/spread_sorter.cpp index 17d21f0e..8879f374 100644 --- a/testsuite/sorters/spread_sorter.cpp +++ b/testsuite/sorters/spread_sorter.cpp @@ -54,11 +54,11 @@ TEST_CASE( "spread_sorter tests", "[spread_sorter]" ) vec.push_back(std::to_string(i)); } - std::shuffle(vec.begin(), vec.end(), random::engine()); + std::shuffle(vec.begin(), vec.end(), hasard::engine()); cppsort::spread_sort(vec); CHECK( std::is_sorted(vec.begin(), vec.end()) ); - std::shuffle(vec.begin(), vec.end(), random::engine()); + std::shuffle(vec.begin(), vec.end(), hasard::engine()); cppsort::spread_sort(vec.begin(), vec.end()); CHECK( std::is_sorted(vec.begin(), vec.end()) ); } @@ -70,11 +70,11 @@ TEST_CASE( "spread_sorter tests", "[spread_sorter]" ) vec.push_back(std::to_string(i)); } - std::shuffle(vec.begin(), vec.end(), random::engine()); + std::shuffle(vec.begin(), vec.end(), hasard::engine()); cppsort::spread_sort(vec, std::greater<>{}); CHECK( std::is_sorted(vec.begin(), vec.end(), std::greater<>{}) ); - std::shuffle(vec.begin(), vec.end(), random::engine()); + std::shuffle(vec.begin(), vec.end(), hasard::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/spread_sorter_defaults.cpp b/testsuite/sorters/spread_sorter_defaults.cpp index 5762e961..b317b198 100644 --- a/testsuite/sorters/spread_sorter_defaults.cpp +++ b/testsuite/sorters/spread_sorter_defaults.cpp @@ -24,11 +24,11 @@ TEST_CASE( "spread_sorter generate overloads", std::vector vec(100'000); std::iota(std::begin(vec), std::end(vec), 0); - std::shuffle(std::begin(vec), std::end(vec), random::engine()); + std::shuffle(std::begin(vec), std::end(vec), hasard::engine()); cppsort::spread_sort(vec, std::less<>{}); CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::less<>{}) ); - std::shuffle(std::begin(vec), std::end(vec), random::engine()); + std::shuffle(std::begin(vec), std::end(vec), hasard::engine()); cppsort::spread_sort(std::begin(vec), std::end(vec), std::less<>{}); CHECK( std::is_sorted(std::begin(vec), std::end(vec), std::less<>{}) ); } @@ -38,11 +38,11 @@ TEST_CASE( "spread_sorter generate overloads", std::vector vec(100'000); std::iota(std::begin(vec), std::end(vec), 0); - std::shuffle(std::begin(vec), std::end(vec), random::engine()); + std::shuffle(std::begin(vec), std::end(vec), hasard::engine()); cppsort::spread_sort(vec, cppsort::utility::identity{}); CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); - std::shuffle(std::begin(vec), std::end(vec), random::engine()); + std::shuffle(std::begin(vec), std::end(vec), hasard::engine()); cppsort::spread_sort(std::begin(vec), std::end(vec), cppsort::utility::identity{}); CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); } @@ -52,11 +52,11 @@ TEST_CASE( "spread_sorter generate overloads", std::vector vec(100'000); std::iota(std::begin(vec), std::end(vec), 0); - std::shuffle(std::begin(vec), std::end(vec), random::engine()); + std::shuffle(std::begin(vec), std::end(vec), hasard::engine()); cppsort::spread_sort(vec, std::less<>{}, cppsort::utility::identity{}); CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); - std::shuffle(std::begin(vec), std::end(vec), random::engine()); + std::shuffle(std::begin(vec), std::end(vec), hasard::engine()); cppsort::spread_sort(std::begin(vec), std::end(vec), std::less<>{}, cppsort::utility::identity{}); CHECK( std::is_sorted(std::begin(vec), std::end(vec)) ); } diff --git a/testsuite/sorters/spread_sorter_projection.cpp b/testsuite/sorters/spread_sorter_projection.cpp index 8f8477f6..cf33f9cf 100644 --- a/testsuite/sorters/spread_sorter_projection.cpp +++ b/testsuite/sorters/spread_sorter_projection.cpp @@ -22,7 +22,7 @@ TEST_CASE( "spread_sorter tests with projections", for (int i = 0 ; i < 100'000 ; ++i) { vec.emplace_back(i, float(i)); } - std::shuffle(std::begin(vec), std::end(vec), random::engine()); + std::shuffle(std::begin(vec), std::end(vec), hasard::engine()); cppsort::spread_sort(vec, &std::pair::first); CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::less<>{}, &std::pair::second) ); @@ -34,7 +34,7 @@ TEST_CASE( "spread_sorter tests with projections", for (int i = 0 ; i < 100'000 ; ++i) { vec.emplace_back(i, float(i)); } - std::shuffle(std::begin(vec), std::end(vec), random::engine()); + std::shuffle(std::begin(vec), std::end(vec), hasard::engine()); cppsort::spread_sort(vec, &std::pair::first); CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::less<>{}, &std::pair::second) ); @@ -46,7 +46,7 @@ TEST_CASE( "spread_sorter tests with projections", for (int i = 0 ; i < 100'000 ; ++i) { vec.emplace_back(i, float(i)); } - std::shuffle(std::begin(vec), std::end(vec), random::engine()); + std::shuffle(std::begin(vec), std::end(vec), hasard::engine()); cppsort::spread_sort(vec, &std::pair::second); CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::less<>{}, &std::pair::first) ); @@ -58,7 +58,7 @@ TEST_CASE( "spread_sorter tests with projections", for (int i = 0 ; i < 100'000 ; ++i) { vec.emplace_back(i, double(i)); } - std::shuffle(std::begin(vec), std::end(vec), random::engine()); + std::shuffle(std::begin(vec), std::end(vec), hasard::engine()); cppsort::spread_sort(vec, &std::pair::second); CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::less<>{}, &std::pair::first) ); @@ -72,7 +72,7 @@ TEST_CASE( "spread_sorter tests with projections", for (int i = 0 ; i < 100'000 ; ++i) { vec.emplace_back(std::to_string(i)); } - std::shuffle(std::begin(vec), std::end(vec), random::engine()); + std::shuffle(std::begin(vec), std::end(vec), hasard::engine()); cppsort::spread_sort(vec, &wrapper::value); CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::less<>{}, &wrapper::value) ); @@ -86,7 +86,7 @@ TEST_CASE( "spread_sorter tests with projections", for (int i = 0 ; i < 100'000 ; ++i) { vec.emplace_back(std::to_string(i)); } - std::shuffle(std::begin(vec), std::end(vec), random::engine()); + std::shuffle(std::begin(vec), std::end(vec), hasard::engine()); cppsort::spread_sort(vec, std::greater<>{}, &wrapper::value); CHECK( helpers::is_sorted(std::begin(vec), std::end(vec), std::greater<>{}, &wrapper::value) ); diff --git a/testsuite/testing-tools/distributions.h b/testsuite/testing-tools/distributions.h index 96fd6d64..b1e4fffb 100644 --- a/testsuite/testing-tools/distributions.h +++ b/testsuite/testing-tools/distributions.h @@ -50,7 +50,7 @@ namespace dist auto operator()(OutputIterator out, long long int size, long long int start=0ll) const -> void { - random::fill_with_shuffle(out, size, start, random::bit_gen()); + hasard::fill_with_shuffle(out, size, start, hasard::bit_gen()); } }; @@ -67,7 +67,7 @@ namespace dist auto operator()(OutputIterator out, long long int size) const -> void { - random::fill_with_shuffle(out, size, 0, random::bit_gen(), &mod_16); + hasard::fill_with_shuffle(out, size, 0, hasard::bit_gen(), &mod_16); } }; diff --git a/testsuite/testing-tools/random.cpp b/testsuite/testing-tools/random.cpp index 5d51de01..0f7f2677 100644 --- a/testsuite/testing-tools/random.cpp +++ b/testsuite/testing-tools/random.cpp @@ -5,7 +5,7 @@ #include #include "random.h" -namespace random +namespace hasard { auto engine() -> xoshiro256ss& diff --git a/testsuite/testing-tools/random.h b/testsuite/testing-tools/random.h index 369b1c64..fc36d03c 100644 --- a/testsuite/testing-tools/random.h +++ b/testsuite/testing-tools/random.h @@ -15,7 +15,7 @@ #include #include -namespace random +namespace hasard // Blame POSIX for picking the good name { //////////////////////////////////////////////////////////// // xoshiro256** From 5b19494197e4d8416b91841efed1670613e9fd81 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 1 Dec 2021 23:02:13 +0100 Subject: [PATCH 77/79] Update measures of presortedness benchmark --- docs/Benchmarks.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/Benchmarks.md b/docs/Benchmarks.md index a0c1316d..43be3e95 100644 --- a/docs/Benchmarks.md +++ b/docs/Benchmarks.md @@ -1,6 +1,6 @@ *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 meaningful update: 1.9.0 release.* +*Last meaningful update: 1.9.0 release, 1.12.0 for measures of presortedness.* 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. @@ -169,11 +169,12 @@ The spikes in the otherwise smooth sorting networks curve when sorting arrays of 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/5Hxpb37.png) +![Benchmark speed of measures of presortedness for increasing size for std::vector](https://i.imgur.com/pjc7zJF.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)*. +It makes rather easy to see the different groups of complexities: +* *Run(X)* and *Mono(X)* are obvious O(n) algorithms. +* *Dis(X)* is a more involved O(n) algorithm. +* All of the other measures of presortedness run in O(n log n) time. [measures-of-presortedness]: https://github.com/Morwenn/cpp-sort/wiki/Measures-of-presortedness From b5a159ffbccbe3b5ac40fa30818bfc516d24e91c Mon Sep 17 00:00:00 2001 From: Morwenn Date: Wed, 1 Dec 2021 23:18:44 +0100 Subject: [PATCH 78/79] Fix benchmark includes --- benchmarks/errorbar-plot/bench.cpp | 6 +++--- benchmarks/inversions/inv-bench.cpp | 10 +++++----- benchmarks/patterns/bench.cpp | 4 ++-- benchmarks/small-array/benchmark.cpp | 6 +++--- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/benchmarks/errorbar-plot/bench.cpp b/benchmarks/errorbar-plot/bench.cpp index 8df9618d..61015249 100644 --- a/benchmarks/errorbar-plot/bench.cpp +++ b/benchmarks/errorbar-plot/bench.cpp @@ -17,9 +17,9 @@ #include #include #include -#include "distributions.h" -#include "filesystem.h" -#include "statistics.h" +#include "../benchmarking-tools/distributions.h" +#include "../benchmarking-tools/filesystem.h" +#include "../benchmarking-tools/statistics.h" using namespace std::chrono_literals; diff --git a/benchmarks/inversions/inv-bench.cpp b/benchmarks/inversions/inv-bench.cpp index a1df50d6..eb45e5f7 100644 --- a/benchmarks/inversions/inv-bench.cpp +++ b/benchmarks/inversions/inv-bench.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Morwenn + * Copyright (c) 2020-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -14,10 +14,10 @@ #include #include #include -#include "distributions.h" -#include "filesystem.h" -#include "rdtsc.h" -#include "statistics.h" +#include "../benchmarking-tools/distributions.h" +#include "../benchmarking-tools/filesystem.h" +#include "../benchmarking-tools/rdtsc.h" +#include "../benchmarking-tools/statistics.h" using namespace std::chrono_literals; diff --git a/benchmarks/patterns/bench.cpp b/benchmarks/patterns/bench.cpp index 30f4c848..7dcf02b2 100644 --- a/benchmarks/patterns/bench.cpp +++ b/benchmarks/patterns/bench.cpp @@ -36,8 +36,8 @@ #include #include #include -#include "distributions.h" -#include "rdtsc.h" +#include "../benchmarking-tools/distributions.h" +#include "../benchmarking-tools/rdtsc.h" // Type of data to sort during the benchmark using value_t = double; diff --git a/benchmarks/small-array/benchmark.cpp b/benchmarks/small-array/benchmark.cpp index 99addbb8..712ae60e 100644 --- a/benchmarks/small-array/benchmark.cpp +++ b/benchmarks/small-array/benchmark.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2020 Morwenn + * Copyright (c) 2015-2021 Morwenn * SPDX-License-Identifier: MIT */ #include @@ -16,8 +16,8 @@ #include #include #include -#include "distributions.h" -#include "rdtsc.h" +#include "../benchmarking-tools/distributions.h" +#include "../benchmarking-tools/rdtsc.h" using namespace std::chrono_literals; From 266707b88915d55de4ff1d5d307d9b2d59204a52 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Thu, 2 Dec 2021 15:59:20 +0100 Subject: [PATCH 79/79] Preparing release 1.12.0 --- CMakeLists.txt | 2 +- README.md | 4 ++-- conanfile.py | 2 +- docs/Home.md | 2 +- docs/Tooling.md | 4 ++-- include/cpp-sort/version.h | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8eadcf5b..a014b15b 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.11.0 LANGUAGES CXX) +project(cpp-sort VERSION 1.12.0 LANGUAGES CXX) include(CMakePackageConfigHelpers) include(GNUInstallDirs) diff --git a/README.md b/README.md index 65bd6370..834a6574 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -[![Latest Release](https://img.shields.io/badge/release-1.11.0-blue.svg)](https://github.com/Morwenn/cpp-sort/releases/tag/1.11.0) -[![Conan Package](https://img.shields.io/badge/conan-cpp--sort%2F1.11.0-blue.svg)](https://conan.io/center/cpp-sort?version=1.11.0) +[![Latest Release](https://img.shields.io/badge/release-1.12.0-blue.svg)](https://github.com/Morwenn/cpp-sort/releases/tag/1.12.0) +[![Conan Package](https://img.shields.io/badge/conan-cpp--sort%2F1.12.0-blue.svg)](https://conan.io/center/cpp-sort?version=1.12.0) [![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 deb99e82..0c0cc3b4 100644 --- a/conanfile.py +++ b/conanfile.py @@ -10,7 +10,7 @@ class CppSortConan(ConanFile): name = "cpp-sort" - version = "1.11.0" + version = "1.12.0" description = "Additional sorting algorithms & related tools" topics = "conan", "cpp-sort", "sorting", "algorithms" url = "https://github.com/Morwenn/cpp-sort" diff --git a/docs/Home.md b/docs/Home.md index a7d33604..c9239310 100644 --- a/docs/Home.md +++ b/docs/Home.md @@ -1,4 +1,4 @@ -Welcome to the **cpp-sort 1.11.0** documentation! +Welcome to the **cpp-sort 1.12.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 0b7c6c28..05c24c33 100644 --- a/docs/Tooling.md +++ b/docs/Tooling.md @@ -51,10 +51,10 @@ The same options exist without the `CPPSORT_` prefix exist, but are deprecated. conan search cpp-sort --remote=conan-center ``` -And then install any version to your local cache as follows (here with version 1.11.0): +And then install any version to your local cache as follows (here with version 1.12.0): ```sh -conan install cpp-sort/1.11.0 +conan install cpp-sort/1.12.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 build your own package with the `conanfile.py` available in this repository. diff --git a/include/cpp-sort/version.h b/include/cpp-sort/version.h index 3e9237cc..f58675ad 100644 --- a/include/cpp-sort/version.h +++ b/include/cpp-sort/version.h @@ -8,7 +8,7 @@ // Semantic versioning macros #define CPPSORT_VERSION_MAJOR 1 -#define CPPSORT_VERSION_MINOR 11 +#define CPPSORT_VERSION_MINOR 12 #define CPPSORT_VERSION_PATCH 0 #endif // CPPSORT_VERSION_H_