[libc++] Make std::stable_sort constexpr friendly (#110320)

Implementing `constexpr std::stable_sort`. This is part of P2562R1,
tracked via issue #105360.

Closes #119394

Co-authored-by: A. Jiang <de34@live.cn>
Co-authored-by: Louis Dionne <ldionne.2@gmail.com>
This commit is contained in:
PaulXiCao 2025-01-14 16:24:35 +01:00 committed by GitHub
parent b1751faada
commit 438e2ccd4a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 289 additions and 233 deletions

View File

@ -2,7 +2,7 @@
"`P2497R0 <https://wg21.link/P2497R0>`__","Testing for success or failure of ``<charconv>`` functions","2023-06 (Varna)","|Complete|","18",""
"`P2592R3 <https://wg21.link/P2592R3>`__","Hashing support for ``std::chrono`` value classes","2023-06 (Varna)","","",""
"`P2587R3 <https://wg21.link/P2587R3>`__","``to_string`` or not ``to_string``","2023-06 (Varna)","","",""
"`P2562R1 <https://wg21.link/P2562R1>`__","``constexpr`` Stable Sorting","2023-06 (Varna)","","",""
"`P2562R1 <https://wg21.link/P2562R1>`__","``constexpr`` Stable Sorting","2023-06 (Varna)","|Partial|","20.0",""
"`P2545R4 <https://wg21.link/P2545R4>`__","Read-Copy Update (RCU)","2023-06 (Varna)","","",""
"`P2530R3 <https://wg21.link/P2530R3>`__","Hazard Pointers for C++26","2023-06 (Varna)","","",""
"`P2538R1 <https://wg21.link/P2538R1>`__","ADL-proof ``std::projected``","2023-06 (Varna)","|Complete|","18",""

1 Paper # Paper Name Meeting Status First released version Notes
2 `P2497R0 <https://wg21.link/P2497R0>`__ Testing for success or failure of ``<charconv>`` functions 2023-06 (Varna) |Complete| 18
3 `P2592R3 <https://wg21.link/P2592R3>`__ Hashing support for ``std::chrono`` value classes 2023-06 (Varna)
4 `P2587R3 <https://wg21.link/P2587R3>`__ ``to_string`` or not ``to_string`` 2023-06 (Varna)
5 `P2562R1 <https://wg21.link/P2562R1>`__ ``constexpr`` Stable Sorting 2023-06 (Varna) |Partial| 20.0
6 `P2545R4 <https://wg21.link/P2545R4>`__ Read-Copy Update (RCU) 2023-06 (Varna)
7 `P2530R3 <https://wg21.link/P2530R3>`__ Hazard Pointers for C++26 2023-06 (Varna)
8 `P2538R1 <https://wg21.link/P2538R1>`__ ADL-proof ``std::projected`` 2023-06 (Varna) |Complete| 18

View File

@ -44,17 +44,17 @@ private:
_Predicate __p_;
public:
_LIBCPP_HIDE_FROM_ABI __invert() {}
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 __invert() {}
_LIBCPP_HIDE_FROM_ABI explicit __invert(_Predicate __p) : __p_(__p) {}
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 explicit __invert(_Predicate __p) : __p_(__p) {}
template <class _T1>
_LIBCPP_HIDE_FROM_ABI bool operator()(const _T1& __x) {
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 bool operator()(const _T1& __x) {
return !__p_(__x);
}
template <class _T1, class _T2>
_LIBCPP_HIDE_FROM_ABI bool operator()(const _T1& __x, const _T2& __y) {
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 bool operator()(const _T1& __x, const _T2& __y) {
return __p_(__y, __x);
}
};
@ -66,7 +66,7 @@ template <class _AlgPolicy,
class _InputIterator2,
class _Sent2,
class _OutputIterator>
_LIBCPP_HIDE_FROM_ABI void __half_inplace_merge(
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void __half_inplace_merge(
_InputIterator1 __first1,
_Sent1 __last1,
_InputIterator2 __first2,
@ -91,7 +91,7 @@ _LIBCPP_HIDE_FROM_ABI void __half_inplace_merge(
}
template <class _AlgPolicy, class _Compare, class _BidirectionalIterator>
_LIBCPP_HIDE_FROM_ABI void __buffered_inplace_merge(
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void __buffered_inplace_merge(
_BidirectionalIterator __first,
_BidirectionalIterator __middle,
_BidirectionalIterator __last,
@ -122,7 +122,7 @@ _LIBCPP_HIDE_FROM_ABI void __buffered_inplace_merge(
}
template <class _AlgPolicy, class _Compare, class _BidirectionalIterator>
void __inplace_merge(
_LIBCPP_CONSTEXPR_SINCE_CXX26 void __inplace_merge(
_BidirectionalIterator __first,
_BidirectionalIterator __middle,
_BidirectionalIterator __last,

View File

@ -240,7 +240,7 @@ __selection_sort(_BidirectionalIterator __first, _BidirectionalIterator __last,
// Sort the iterator range [__first, __last) using the comparator __comp using
// the insertion sort algorithm.
template <class _AlgPolicy, class _Compare, class _BidirectionalIterator>
_LIBCPP_HIDE_FROM_ABI void
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void
__insertion_sort(_BidirectionalIterator __first, _BidirectionalIterator __last, _Compare __comp) {
using _Ops = _IterOps<_AlgPolicy>;

View File

@ -19,6 +19,7 @@
#include <__cstddef/ptrdiff_t.h>
#include <__debug_utils/strict_weak_ordering_check.h>
#include <__iterator/iterator_traits.h>
#include <__memory/construct_at.h>
#include <__memory/destruct_n.h>
#include <__memory/unique_ptr.h>
#include <__memory/unique_temporary_buffer.h>
@ -41,7 +42,7 @@ _LIBCPP_PUSH_MACROS
_LIBCPP_BEGIN_NAMESPACE_STD
template <class _AlgPolicy, class _Compare, class _BidirectionalIterator>
_LIBCPP_HIDE_FROM_ABI void __insertion_sort_move(
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void __insertion_sort_move(
_BidirectionalIterator __first1,
_BidirectionalIterator __last1,
typename iterator_traits<_BidirectionalIterator>::value_type* __first2,
@ -53,19 +54,19 @@ _LIBCPP_HIDE_FROM_ABI void __insertion_sort_move(
__destruct_n __d(0);
unique_ptr<value_type, __destruct_n&> __h(__first2, __d);
value_type* __last2 = __first2;
::new ((void*)__last2) value_type(_Ops::__iter_move(__first1));
std::__construct_at(__last2, _Ops::__iter_move(__first1));
__d.template __incr<value_type>();
for (++__last2; ++__first1 != __last1; ++__last2) {
value_type* __j2 = __last2;
value_type* __i2 = __j2;
if (__comp(*__first1, *--__i2)) {
::new ((void*)__j2) value_type(std::move(*__i2));
std::__construct_at(__j2, std::move(*__i2));
__d.template __incr<value_type>();
for (--__j2; __i2 != __first2 && __comp(*__first1, *--__i2); --__j2)
*__j2 = std::move(*__i2);
*__j2 = _Ops::__iter_move(__first1);
} else {
::new ((void*)__j2) value_type(_Ops::__iter_move(__first1));
std::__construct_at(__j2, _Ops::__iter_move(__first1));
__d.template __incr<value_type>();
}
}
@ -74,7 +75,7 @@ _LIBCPP_HIDE_FROM_ABI void __insertion_sort_move(
}
template <class _AlgPolicy, class _Compare, class _InputIterator1, class _InputIterator2>
_LIBCPP_HIDE_FROM_ABI void __merge_move_construct(
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void __merge_move_construct(
_InputIterator1 __first1,
_InputIterator1 __last1,
_InputIterator2 __first2,
@ -89,22 +90,22 @@ _LIBCPP_HIDE_FROM_ABI void __merge_move_construct(
for (; true; ++__result) {
if (__first1 == __last1) {
for (; __first2 != __last2; ++__first2, (void)++__result, __d.template __incr<value_type>())
::new ((void*)__result) value_type(_Ops::__iter_move(__first2));
std::__construct_at(__result, _Ops::__iter_move(__first2));
__h.release();
return;
}
if (__first2 == __last2) {
for (; __first1 != __last1; ++__first1, (void)++__result, __d.template __incr<value_type>())
::new ((void*)__result) value_type(_Ops::__iter_move(__first1));
std::__construct_at(__result, _Ops::__iter_move(__first1));
__h.release();
return;
}
if (__comp(*__first2, *__first1)) {
::new ((void*)__result) value_type(_Ops::__iter_move(__first2));
std::__construct_at(__result, _Ops::__iter_move(__first2));
__d.template __incr<value_type>();
++__first2;
} else {
::new ((void*)__result) value_type(_Ops::__iter_move(__first1));
std::__construct_at(__result, _Ops::__iter_move(__first1));
__d.template __incr<value_type>();
++__first1;
}
@ -112,7 +113,7 @@ _LIBCPP_HIDE_FROM_ABI void __merge_move_construct(
}
template <class _AlgPolicy, class _Compare, class _InputIterator1, class _InputIterator2, class _OutputIterator>
_LIBCPP_HIDE_FROM_ABI void __merge_move_assign(
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void __merge_move_assign(
_InputIterator1 __first1,
_InputIterator1 __last1,
_InputIterator2 __first2,
@ -140,19 +141,21 @@ _LIBCPP_HIDE_FROM_ABI void __merge_move_assign(
}
template <class _AlgPolicy, class _Compare, class _RandomAccessIterator>
void __stable_sort(_RandomAccessIterator __first,
_RandomAccessIterator __last,
_Compare __comp,
typename iterator_traits<_RandomAccessIterator>::difference_type __len,
typename iterator_traits<_RandomAccessIterator>::value_type* __buff,
ptrdiff_t __buff_size);
_LIBCPP_CONSTEXPR_SINCE_CXX26 void __stable_sort(
_RandomAccessIterator __first,
_RandomAccessIterator __last,
_Compare __comp,
typename iterator_traits<_RandomAccessIterator>::difference_type __len,
typename iterator_traits<_RandomAccessIterator>::value_type* __buff,
ptrdiff_t __buff_size);
template <class _AlgPolicy, class _Compare, class _RandomAccessIterator>
void __stable_sort_move(_RandomAccessIterator __first1,
_RandomAccessIterator __last1,
_Compare __comp,
typename iterator_traits<_RandomAccessIterator>::difference_type __len,
typename iterator_traits<_RandomAccessIterator>::value_type* __first2) {
_LIBCPP_CONSTEXPR_SINCE_CXX26 void __stable_sort_move(
_RandomAccessIterator __first1,
_RandomAccessIterator __last1,
_Compare __comp,
typename iterator_traits<_RandomAccessIterator>::difference_type __len,
typename iterator_traits<_RandomAccessIterator>::value_type* __first2) {
using _Ops = _IterOps<_AlgPolicy>;
typedef typename iterator_traits<_RandomAccessIterator>::value_type value_type;
@ -160,21 +163,21 @@ void __stable_sort_move(_RandomAccessIterator __first1,
case 0:
return;
case 1:
::new ((void*)__first2) value_type(_Ops::__iter_move(__first1));
std::__construct_at(__first2, _Ops::__iter_move(__first1));
return;
case 2:
__destruct_n __d(0);
unique_ptr<value_type, __destruct_n&> __h2(__first2, __d);
if (__comp(*--__last1, *__first1)) {
::new ((void*)__first2) value_type(_Ops::__iter_move(__last1));
std::__construct_at(__first2, _Ops::__iter_move(__last1));
__d.template __incr<value_type>();
++__first2;
::new ((void*)__first2) value_type(_Ops::__iter_move(__first1));
std::__construct_at(__first2, _Ops::__iter_move(__first1));
} else {
::new ((void*)__first2) value_type(_Ops::__iter_move(__first1));
std::__construct_at(__first2, _Ops::__iter_move(__first1));
__d.template __incr<value_type>();
++__first2;
::new ((void*)__first2) value_type(_Ops::__iter_move(__last1));
std::__construct_at(__first2, _Ops::__iter_move(__last1));
}
__h2.release();
return;
@ -218,12 +221,13 @@ _LIBCPP_HIDE_FROM_ABI constexpr unsigned __radix_sort_max_bound() {
#endif // _LIBCPP_STD_VER >= 17
template <class _AlgPolicy, class _Compare, class _RandomAccessIterator>
void __stable_sort(_RandomAccessIterator __first,
_RandomAccessIterator __last,
_Compare __comp,
typename iterator_traits<_RandomAccessIterator>::difference_type __len,
typename iterator_traits<_RandomAccessIterator>::value_type* __buff,
ptrdiff_t __buff_size) {
_LIBCPP_CONSTEXPR_SINCE_CXX26 void __stable_sort(
_RandomAccessIterator __first,
_RandomAccessIterator __last,
_Compare __comp,
typename iterator_traits<_RandomAccessIterator>::difference_type __len,
typename iterator_traits<_RandomAccessIterator>::value_type* __buff,
ptrdiff_t __buff_size) {
typedef typename iterator_traits<_RandomAccessIterator>::value_type value_type;
typedef typename iterator_traits<_RandomAccessIterator>::difference_type difference_type;
switch (__len) {
@ -279,7 +283,7 @@ void __stable_sort(_RandomAccessIterator __first,
}
template <class _AlgPolicy, class _RandomAccessIterator, class _Compare>
inline _LIBCPP_HIDE_FROM_ABI void
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void
__stable_sort_impl(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare& __comp) {
using value_type = typename iterator_traits<_RandomAccessIterator>::value_type;
using difference_type = typename iterator_traits<_RandomAccessIterator>::difference_type;
@ -298,18 +302,18 @@ __stable_sort_impl(_RandomAccessIterator __first, _RandomAccessIterator __last,
}
template <class _RandomAccessIterator, class _Compare>
inline _LIBCPP_HIDE_FROM_ABI void
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void
stable_sort(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp) {
std::__stable_sort_impl<_ClassicAlgPolicy>(std::move(__first), std::move(__last), __comp);
}
template <class _RandomAccessIterator>
inline _LIBCPP_HIDE_FROM_ABI void stable_sort(_RandomAccessIterator __first, _RandomAccessIterator __last) {
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void
stable_sort(_RandomAccessIterator __first, _RandomAccessIterator __last) {
std::stable_sort(__first, __last, __less<>());
}
_LIBCPP_END_NAMESPACE_STD
_LIBCPP_POP_MACROS
#endif // _LIBCPP___ALGORITHM_STABLE_SORT_H

View File

@ -25,35 +25,35 @@ private:
size_t __size_;
template <class _Tp>
_LIBCPP_HIDE_FROM_ABI void __process(_Tp* __p, false_type) _NOEXCEPT {
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void __process(_Tp* __p, false_type) _NOEXCEPT {
for (size_t __i = 0; __i < __size_; ++__i, ++__p)
__p->~_Tp();
}
template <class _Tp>
_LIBCPP_HIDE_FROM_ABI void __process(_Tp*, true_type) _NOEXCEPT {}
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void __process(_Tp*, true_type) _NOEXCEPT {}
_LIBCPP_HIDE_FROM_ABI void __incr(false_type) _NOEXCEPT { ++__size_; }
_LIBCPP_HIDE_FROM_ABI void __incr(true_type) _NOEXCEPT {}
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void __incr(false_type) _NOEXCEPT { ++__size_; }
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void __incr(true_type) _NOEXCEPT {}
_LIBCPP_HIDE_FROM_ABI void __set(size_t __s, false_type) _NOEXCEPT { __size_ = __s; }
_LIBCPP_HIDE_FROM_ABI void __set(size_t, true_type) _NOEXCEPT {}
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void __set(size_t __s, false_type) _NOEXCEPT { __size_ = __s; }
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void __set(size_t, true_type) _NOEXCEPT {}
public:
_LIBCPP_HIDE_FROM_ABI explicit __destruct_n(size_t __s) _NOEXCEPT : __size_(__s) {}
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 explicit __destruct_n(size_t __s) _NOEXCEPT : __size_(__s) {}
template <class _Tp>
_LIBCPP_HIDE_FROM_ABI void __incr() _NOEXCEPT {
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void __incr() _NOEXCEPT {
__incr(integral_constant<bool, is_trivially_destructible<_Tp>::value>());
}
template <class _Tp>
_LIBCPP_HIDE_FROM_ABI void __set(size_t __s, _Tp*) _NOEXCEPT {
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void __set(size_t __s, _Tp*) _NOEXCEPT {
__set(__s, integral_constant<bool, is_trivially_destructible<_Tp>::value>());
}
template <class _Tp>
_LIBCPP_HIDE_FROM_ABI void operator()(_Tp* __p) _NOEXCEPT {
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void operator()(_Tp* __p) _NOEXCEPT {
__process(__p, integral_constant<bool, is_trivially_destructible<_Tp>::value>());
}
};

View File

@ -1530,11 +1530,11 @@ template <class RandomAccessIterator, class Compare>
sort(RandomAccessIterator first, RandomAccessIterator last, Compare comp);
template <class RandomAccessIterator>
void
constexpr void // constexpr in C++26
stable_sort(RandomAccessIterator first, RandomAccessIterator last);
template <class RandomAccessIterator, class Compare>
void
constexpr void // constexpr in C++26
stable_sort(RandomAccessIterator first, RandomAccessIterator last, Compare comp);
template <class RandomAccessIterator>

View File

@ -819,7 +819,10 @@ module std [system] {
module sort_heap { header "__algorithm/sort_heap.h" }
module sort { header "__algorithm/sort.h" }
module stable_partition { header "__algorithm/stable_partition.h" }
module stable_sort { header "__algorithm/stable_sort.h" }
module stable_sort {
header "__algorithm/stable_sort.h"
export std.memory.unique_temporary_buffer // TODO: Workaround for https://github.com/llvm/llvm-project/issues/120108
}
module swap_ranges { header "__algorithm/swap_ranges.h" }
module three_way_comp_ref_type { header "__algorithm/three_way_comp_ref_type.h" }
module transform { header "__algorithm/transform.h" }

View File

@ -8,11 +8,12 @@
// <algorithm>
// template<RandomAccessIterator Iter>
// requires ShuffleIterator<Iter>
// && LessThanComparable<Iter::value_type>
// void
// stable_sort(Iter first, Iter last);
// template <class RandomAccessIterator>
// constexpr void // constexpr since C++26
// stable_sort(RandomAccessIterator first, RandomAccessIterator last);
// ADDITIONAL_COMPILE_FLAGS(has-fconstexpr-steps): -fconstexpr-steps=200000000
// ADDITIONAL_COMPILE_FLAGS(has-fconstexpr-ops-limit): -fconstexpr-ops-limit=200000000
#include <algorithm>
#include <cassert>
@ -23,156 +24,181 @@
#include "count_new.h"
#include "test_macros.h"
std::mt19937 randomness;
template <class Iterator>
TEST_CONSTEXPR_CXX26 void test_all_permutations(Iterator first, Iterator last) {
using T = typename std::iterator_traits<Iterator>::value_type;
template <class RI>
void
test_sort_helper(RI f, RI l)
{
typedef typename std::iterator_traits<RI>::value_type value_type;
typedef typename std::iterator_traits<RI>::difference_type difference_type;
if (f != l)
{
difference_type len = l - f;
value_type* save(new value_type[len]);
do
{
std::copy(f, l, save);
std::stable_sort(save, save+len);
assert(std::is_sorted(save, save+len));
} while (std::next_permutation(f, l));
delete [] save;
}
do {
std::vector<T> save(first, last);
std::stable_sort(save.begin(), save.end());
assert(std::is_sorted(save.begin(), save.end()));
} while (std::next_permutation(first, last));
}
template <class RI>
void
test_sort_driver_driver(RI f, RI l, int start, RI real_last)
{
using value_type = typename std::iterator_traits<RI>::value_type;
template <class Iterator>
TEST_CONSTEXPR_CXX26 void test_sort_exhaustive_impl(Iterator first, Iterator last, int start, Iterator real_last) {
using T = typename std::iterator_traits<Iterator>::value_type;
for (RI i = l; i > f + start;) {
*--i = static_cast<value_type>(start);
if (f == i) {
test_sort_helper(f, real_last);
for (Iterator i = last; i > first + start;) {
*--i = static_cast<T>(start);
if (first == i) {
test_all_permutations(first, real_last);
}
if (start > 0)
test_sort_driver_driver(f, i, start-1, real_last);
test_sort_exhaustive_impl(first, i, start - 1, real_last);
}
}
template <class RI>
void
test_sort_driver(RI f, RI l, int start)
{
test_sort_driver_driver(f, l, start, l);
}
template <int sa, class V>
void test_sort_() {
V ia[sa];
for (int i = 0; i < sa; ++i) {
test_sort_driver(ia, ia + sa, i);
}
}
template <int sa>
void test_sort_() {
test_sort_<sa, int>();
test_sort_<sa, float>();
}
template <class V>
void test_larger_sorts(int N, int M) {
assert(N != 0);
assert(M != 0);
// create array length N filled with M different numbers
V* array = new V[N];
int x = 0;
template <class T>
TEST_CONSTEXPR_CXX26 void test_sort_exhaustive(int N) {
std::vector<T> vec;
vec.resize(N);
for (int i = 0; i < N; ++i) {
array[i] = static_cast<V>(x);
test_sort_exhaustive_impl(vec.begin(), vec.end(), i, vec.end());
}
}
template <class T>
TEST_CONSTEXPR_CXX26 std::vector<T> generate_sawtooth(int N, int M) {
// Populate a sequence of length N with M different numbers
std::vector<T> v;
T x = 0;
for (int i = 0; i < N; ++i) {
v.push_back(x);
if (++x == M)
x = 0;
}
return v;
}
template <class T>
TEST_CONSTEXPR_CXX26 void test_larger_sorts(int N, int M) {
assert(N != 0);
assert(M != 0);
// test saw tooth pattern
std::stable_sort(array, array + N);
assert(std::is_sorted(array, array + N));
{
auto v = generate_sawtooth<T>(N, M);
std::stable_sort(v.begin(), v.end());
assert(std::is_sorted(v.begin(), v.end()));
}
// test random pattern
std::shuffle(array, array + N, randomness);
std::stable_sort(array, array + N);
assert(std::is_sorted(array, array + N));
// test sorted pattern
std::stable_sort(array, array + N);
assert(std::is_sorted(array, array + N));
// test reverse sorted pattern
std::reverse(array, array + N);
std::stable_sort(array, array + N);
assert(std::is_sorted(array, array + N));
// test swap ranges 2 pattern
std::swap_ranges(array, array + N / 2, array + N / 2);
std::stable_sort(array, array + N);
assert(std::is_sorted(array, array + N));
// test reverse swap ranges 2 pattern
std::reverse(array, array + N);
std::swap_ranges(array, array + N / 2, array + N / 2);
std::stable_sort(array, array + N);
assert(std::is_sorted(array, array + N));
delete[] array;
}
void test_larger_sorts(int N, int M) {
test_larger_sorts<int>(N, M);
test_larger_sorts<float>(N, M);
}
void
test_larger_sorts(int N)
{
test_larger_sorts(N, 1);
test_larger_sorts(N, 2);
test_larger_sorts(N, 3);
test_larger_sorts(N, N/2-1);
test_larger_sorts(N, N/2);
test_larger_sorts(N, N/2+1);
test_larger_sorts(N, N-2);
test_larger_sorts(N, N-1);
test_larger_sorts(N, N);
}
int main(int, char**)
{
// test null range
int d = 0;
std::stable_sort(&d, &d);
// exhaustively test all possibilities up to length 8
test_sort_<1>();
test_sort_<2>();
test_sort_<3>();
test_sort_<4>();
test_sort_<5>();
test_sort_<6>();
test_sort_<7>();
test_sort_<8>();
test_larger_sorts(256);
test_larger_sorts(257);
test_larger_sorts(499);
test_larger_sorts(500);
test_larger_sorts(997);
test_larger_sorts(1000);
test_larger_sorts(1009);
test_larger_sorts(1024);
test_larger_sorts(1031);
test_larger_sorts(2053);
#if !defined(TEST_HAS_NO_EXCEPTIONS)
{ // check that the algorithm works without memory
std::vector<int> vec(150, 3);
getGlobalMemCounter()->throw_after = 0;
std::stable_sort(vec.begin(), vec.end());
{
if (!TEST_IS_CONSTANT_EVALUATED) {
auto v = generate_sawtooth<T>(N, M);
std::mt19937 randomness;
std::shuffle(v.begin(), v.end(), randomness);
std::stable_sort(v.begin(), v.end());
assert(std::is_sorted(v.begin(), v.end()));
}
#endif // !defined(TEST_HAS_NO_EXCEPTIONS)
}
// test sorted pattern
{
auto v = generate_sawtooth<T>(N, M);
std::sort(v.begin(), v.end());
std::stable_sort(v.begin(), v.end());
assert(std::is_sorted(v.begin(), v.end()));
}
// test reverse sorted pattern
{
auto v = generate_sawtooth<T>(N, M);
std::sort(v.begin(), v.end());
std::reverse(v.begin(), v.end());
std::stable_sort(v.begin(), v.end());
assert(std::is_sorted(v.begin(), v.end()));
}
// test swap ranges 2 pattern
{
auto v = generate_sawtooth<T>(N, M);
std::sort(v.begin(), v.end());
std::swap_ranges(v.begin(), v.begin() + (N / 2), v.begin() + (N / 2));
std::stable_sort(v.begin(), v.end());
assert(std::is_sorted(v.begin(), v.end()));
}
// test reverse swap ranges 2 pattern
{
auto v = generate_sawtooth<T>(N, M);
std::sort(v.begin(), v.end());
std::reverse(v.begin(), v.end());
std::swap_ranges(v.begin(), v.begin() + (N / 2), v.begin() + (N / 2));
std::stable_sort(v.begin(), v.end());
assert(std::is_sorted(v.begin(), v.end()));
}
}
template <class T>
TEST_CONSTEXPR_CXX26 void test_larger_sorts(int N) {
test_larger_sorts<T>(N, 1);
test_larger_sorts<T>(N, 2);
test_larger_sorts<T>(N, 3);
test_larger_sorts<T>(N, N / 2 - 1);
test_larger_sorts<T>(N, N / 2);
test_larger_sorts<T>(N, N / 2 + 1);
test_larger_sorts<T>(N, N - 2);
test_larger_sorts<T>(N, N - 1);
test_larger_sorts<T>(N, N);
}
template <class T>
TEST_CONSTEXPR_CXX26 bool test() {
// test null range
{
T value = 0;
std::stable_sort(&value, &value);
}
// exhaustively test all possibilities up to length 8
if (!TEST_IS_CONSTANT_EVALUATED) {
test_sort_exhaustive<T>(1);
test_sort_exhaustive<T>(2);
test_sort_exhaustive<T>(3);
test_sort_exhaustive<T>(4);
test_sort_exhaustive<T>(5);
test_sort_exhaustive<T>(6);
test_sort_exhaustive<T>(7);
test_sort_exhaustive<T>(8);
}
test_larger_sorts<T>(256);
test_larger_sorts<T>(257);
if (!TEST_IS_CONSTANT_EVALUATED) { // avoid blowing past constexpr evaluation limit
test_larger_sorts<T>(499);
test_larger_sorts<T>(500);
test_larger_sorts<T>(997);
test_larger_sorts<T>(1000);
test_larger_sorts<T>(1009);
test_larger_sorts<T>(1024);
test_larger_sorts<T>(1031);
test_larger_sorts<T>(2053);
}
// check that the algorithm works without memory
#ifndef TEST_HAS_NO_EXCEPTIONS
if (!TEST_IS_CONSTANT_EVALUATED) {
std::vector<T> vec(150, T(3));
getGlobalMemCounter()->throw_after = 0;
std::stable_sort(vec.begin(), vec.end());
}
#endif
return true;
}
int main(int, char**) {
test<int>();
test<float>();
#if TEST_STD_VER >= 26
static_assert(test<int>());
static_assert(test<float>());
#endif
return 0;
}

View File

@ -9,10 +9,11 @@
// <algorithm>
// template<RandomAccessIterator Iter, StrictWeakOrder<auto, Iter::value_type> Compare>
// requires ShuffleIterator<Iter>
// && CopyConstructible<Compare>
// void
// stable_sort(Iter first, Iter last, Compare comp);
// requires ShuffleIterator<Iter> && CopyConstructible<Compare>
// constexpr void stable_sort(Iter first, Iter last, Compare comp); // constexpr since C++26
//
// ADDITIONAL_COMPILE_FLAGS(has-fconstexpr-steps): -fconstexpr-steps=200000000
// ADDITIONAL_COMPILE_FLAGS(has-fconstexpr-ops-limit): -fconstexpr-ops-limit=200000000
#include <algorithm>
#include <functional>
@ -27,57 +28,79 @@
struct indirect_less {
template <class P>
bool operator()(const P& x, const P& y) const {
TEST_CONSTEXPR_CXX26 bool operator()(const P& x, const P& y) const {
return *x < *y;
}
};
std::mt19937 randomness;
struct first_only {
bool operator()(const std::pair<int, int>& x, const std::pair<int, int>& y) const { return x.first < y.first; }
TEST_CONSTEXPR_CXX26 bool operator()(const std::pair<int, int>& x, const std::pair<int, int>& y) const {
return x.first < y.first;
}
};
void test()
{
typedef std::pair<int, int> P;
const int N = 1000;
const int M = 10;
std::vector<P> v(N);
int x = 0;
int ver = 0;
for (int i = 0; i < N; ++i)
{
v[i] = P(x, ver);
if (++x == M)
{
x = 0;
++ver;
}
using Pair = std::pair<int, int>;
TEST_CONSTEXPR_CXX26 std::vector<Pair> generate_sawtooth(int N, int M) {
std::vector<Pair> v(N);
int x = 0;
int ver = 0;
for (int i = 0; i < N; ++i) {
v[i] = Pair(x, ver);
if (++x == M) {
x = 0;
++ver;
}
for (int i = 0; i < N - M; i += M)
{
std::shuffle(v.begin() + i, v.begin() + i + M, randomness);
}
std::stable_sort(v.begin(), v.end(), first_only());
assert(std::is_sorted(v.begin(), v.end()));
}
return v;
}
int main(int, char**)
{
test();
TEST_CONSTEXPR_CXX26 bool test() {
int const N = 1000;
int const M = 10;
// test sawtooth pattern
{
auto v = generate_sawtooth(N, M);
std::stable_sort(v.begin(), v.end(), first_only());
assert(std::is_sorted(v.begin(), v.end()));
}
// Test sorting a sequence where subsequences of elements are not sorted with <,
// but everything is already sorted with respect to the first element. This ensures
// that we don't change the order of "equivalent" elements.
{
if (!TEST_IS_CONSTANT_EVALUATED) {
auto v = generate_sawtooth(N, M);
std::mt19937 randomness;
for (int i = 0; i < N - M; i += M) {
std::shuffle(v.begin() + i, v.begin() + i + M, randomness);
}
std::stable_sort(v.begin(), v.end(), first_only());
assert(std::is_sorted(v.begin(), v.end()));
}
}
#if TEST_STD_VER >= 11
{
{
std::vector<std::unique_ptr<int> > v(1000);
for (int i = 0; static_cast<std::size_t>(i) < v.size(); ++i)
v[i].reset(new int(i));
v[i].reset(new int(i));
std::stable_sort(v.begin(), v.end(), indirect_less());
assert(std::is_sorted(v.begin(), v.end(), indirect_less()));
assert(*v[0] == 0);
assert(*v[1] == 1);
assert(*v[2] == 2);
}
}
#endif
return true;
}
int main(int, char**) {
test();
#if TEST_STD_VER >= 26
static_assert(test());
#endif
return 0;