Skip to content

[libc++] Implement part of P2562R1: constexpr std::inplace_merge #129008

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Mar 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
4ae5ab2
[libc++] Implement constexpr `std::inplace_merge`
frederick-vs-ja Mar 1, 2025
76b5ef4
Clang-format some test files
frederick-vs-ja Mar 1, 2025
cac2d2f
Merge branch 'main' into constexpr-std-inplace_merge
frederick-vs-ja Mar 10, 2025
7b090e1
Change `constexpr in` to `constexpr since` in comments
frederick-vs-ja Mar 10, 2025
0fd8637
Add `constexpr_random.h` for test suite
frederick-vs-ja Mar 10, 2025
b9cb3f5
Use constexpr-ized `minstd_rand` as random source in test files
frederick-vs-ja Mar 10, 2025
ed1b734
Implicitly declare destructor for `binary_counting_predicate`
frederick-vs-ja Mar 10, 2025
b2c8cb8
Reorder `inline` in production code
frederick-vs-ja Mar 10, 2025
7d9cad8
Clang-format `constexpr_random.h`
frederick-vs-ja Mar 10, 2025
b547b44
Merge branch 'main' into constexpr-std-inplace_merge
frederick-vs-ja Mar 10, 2025
bdabdd6
Include `<limits>` in `constexpr_random.h`
frederick-vs-ja Mar 10, 2025
177ec8e
Merge branch 'main' into constexpr-std-inplace_merge
frederick-vs-ja Mar 10, 2025
712c95c
Fix mis-versioned `constexpr` in `constexpr_random.h`
frederick-vs-ja Mar 10, 2025
49a0099
Merge branch 'main' into constexpr-std-inplace_merge
frederick-vs-ja Mar 16, 2025
0c03d72
Address @mordante's review comments
frederick-vs-ja Mar 16, 2025
604a5e9
Fix errors in C++03
frederick-vs-ja Mar 16, 2025
3ef190d
Merge branch 'main' into constexpr-std-inplace_merge
frederick-vs-ja Mar 17, 2025
5e4acac
Restore skipping
frederick-vs-ja Mar 17, 2025
cdc613d
Merge branch 'main' into constexpr-std-inplace_merge
frederick-vs-ja Mar 18, 2025
92a413b
Address @mordante's review comments
frederick-vs-ja Mar 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions libcxx/include/__algorithm/inplace_merge.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <__functional/identity.h>
#include <__iterator/iterator_traits.h>
#include <__iterator/reverse_iterator.h>
#include <__memory/construct_at.h>
#include <__memory/destruct_n.h>
#include <__memory/unique_ptr.h>
#include <__memory/unique_temporary_buffer.h>
Expand Down Expand Up @@ -106,13 +107,13 @@ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void __buffered_inplace_merg
value_type* __p = __buff;
for (_BidirectionalIterator __i = __first; __i != __middle;
__d.template __incr<value_type>(), (void)++__i, (void)++__p)
::new ((void*)__p) value_type(_IterOps<_AlgPolicy>::__iter_move(__i));
std::__construct_at(__p, _IterOps<_AlgPolicy>::__iter_move(__i));
std::__half_inplace_merge<_AlgPolicy>(__buff, __p, __middle, __last, __first, __comp);
} else {
value_type* __p = __buff;
for (_BidirectionalIterator __i = __middle; __i != __last;
__d.template __incr<value_type>(), (void)++__i, (void)++__p)
::new ((void*)__p) value_type(_IterOps<_AlgPolicy>::__iter_move(__i));
std::__construct_at(__p, _IterOps<_AlgPolicy>::__iter_move(__i));
typedef reverse_iterator<_BidirectionalIterator> _RBi;
typedef reverse_iterator<value_type*> _Rv;
typedef __invert<_Compare> _Inverted;
Expand Down Expand Up @@ -203,7 +204,7 @@ _LIBCPP_CONSTEXPR_SINCE_CXX26 void __inplace_merge(
}

template <class _AlgPolicy, class _BidirectionalIterator, class _Compare>
_LIBCPP_HIDE_FROM_ABI void __inplace_merge(
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void __inplace_merge(
_BidirectionalIterator __first, _BidirectionalIterator __middle, _BidirectionalIterator __last, _Compare&& __comp) {
typedef typename iterator_traits<_BidirectionalIterator>::value_type value_type;
typedef typename iterator_traits<_BidirectionalIterator>::difference_type difference_type;
Expand All @@ -223,14 +224,14 @@ _LIBCPP_HIDE_FROM_ABI void __inplace_merge(
}

template <class _BidirectionalIterator, class _Compare>
inline _LIBCPP_HIDE_FROM_ABI void inplace_merge(
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void inplace_merge(
_BidirectionalIterator __first, _BidirectionalIterator __middle, _BidirectionalIterator __last, _Compare __comp) {
std::__inplace_merge<_ClassicAlgPolicy>(
std::move(__first), std::move(__middle), std::move(__last), static_cast<__comp_ref_type<_Compare> >(__comp));
}

template <class _BidirectionalIterator>
inline _LIBCPP_HIDE_FROM_ABI void
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void
inplace_merge(_BidirectionalIterator __first, _BidirectionalIterator __middle, _BidirectionalIterator __last) {
std::inplace_merge(std::move(__first), std::move(__middle), std::move(__last), __less<>());
}
Expand Down
4 changes: 2 additions & 2 deletions libcxx/include/algorithm
Original file line number Diff line number Diff line change
Expand Up @@ -1609,11 +1609,11 @@ template <class InputIterator1, class InputIterator2, class OutputIterator, clas
InputIterator2 first2, InputIterator2 last2, OutputIterator result, Compare comp);

template <class BidirectionalIterator>
void
constexpr void // constexpr since C++26
inplace_merge(BidirectionalIterator first, BidirectionalIterator middle, BidirectionalIterator last);

template <class BidirectionalIterator, class Compare>
void
constexpr void // constexpr since C++26
inplace_merge(BidirectionalIterator first, BidirectionalIterator middle, BidirectionalIterator last, Compare comp);

template <class InputIterator1, class InputIterator2>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,116 +8,130 @@

// <algorithm>

// template<BidirectionalIterator Iter>
// requires ShuffleIterator<Iter>
// && LessThanComparable<Iter::value_type>
// void
// inplace_merge(Iter first, Iter middle, Iter last);
// template<class BidirectionalIterator>
// constexpr void // constexpr since C++26
// inplace_merge(BidirectionalIterator first, BidirectionalIterator middle, BidirectionalIterator last);

#include <algorithm>
#include <cassert>
#include <random>
#include <vector>

#include "count_new.h"
#include "constexpr_random.h"
#include "test_iterators.h"
#include "test_macros.h"

#if TEST_STD_VER >= 11
struct S {
S() : i_(0) {}
S(int i) : i_(i) {}

S(const S& rhs) : i_(rhs.i_) {}
S( S&& rhs) : i_(rhs.i_) { rhs.i_ = -1; }

S& operator =(const S& rhs) { i_ = rhs.i_; return *this; }
S& operator =( S&& rhs) { i_ = rhs.i_; rhs.i_ = -2; assert(this != &rhs); return *this; }
S& operator =(int i) { i_ = i; return *this; }

bool operator <(const S& rhs) const { return i_ < rhs.i_; }
bool operator ==(const S& rhs) const { return i_ == rhs.i_; }
bool operator ==(int i) const { return i_ == i; }

void set(int i) { i_ = i; }

int i_;
};
TEST_CONSTEXPR_CXX26 S() : i_(0) {}
TEST_CONSTEXPR_CXX26 S(int i) : i_(i) {}

TEST_CONSTEXPR_CXX26 S(const S& rhs) : i_(rhs.i_) {}
TEST_CONSTEXPR_CXX26 S(S&& rhs) : i_(rhs.i_) { rhs.i_ = -1; }

TEST_CONSTEXPR_CXX26 S& operator=(const S& rhs) {
i_ = rhs.i_;
return *this;
}
TEST_CONSTEXPR_CXX26 S& operator=(S&& rhs) {
i_ = rhs.i_;
rhs.i_ = -2;
assert(this != &rhs);
return *this;
}
TEST_CONSTEXPR_CXX26 S& operator=(int i) {
i_ = i;
return *this;
}

TEST_CONSTEXPR_CXX26 bool operator<(const S& rhs) const { return i_ < rhs.i_; }
TEST_CONSTEXPR_CXX26 bool operator==(const S& rhs) const { return i_ == rhs.i_; }
TEST_CONSTEXPR_CXX26 bool operator==(int i) const { return i_ == i; }

TEST_CONSTEXPR_CXX26 void set(int i) { i_ = i; }

int i_;
};
#endif

std::mt19937 randomness;

template <class Iter>
void
test_one(unsigned N, unsigned M)
{
typedef typename std::iterator_traits<Iter>::value_type value_type;
assert(M <= N);
value_type* ia = new value_type[N];
for (unsigned i = 0; i < N; ++i)
ia[i] = i;
std::shuffle(ia, ia+N, randomness);
std::sort(ia, ia+M);
std::sort(ia+M, ia+N);
std::inplace_merge(Iter(ia), Iter(ia+M), Iter(ia+N));
if(N > 0)
{
assert(ia[0] == 0);
assert(ia[N-1] == static_cast<value_type>(N-1));
assert(std::is_sorted(ia, ia+N));
}
delete [] ia;
template <class Iter, class RandSrc>
TEST_CONSTEXPR_CXX26 void test_one(unsigned N, unsigned M, RandSrc& randomness) {
assert(M <= N);

typedef typename std::iterator_traits<Iter>::value_type value_type;
value_type* ia = new value_type[N];
for (unsigned i = 0; i < N; ++i)
ia[i] = i;
support::shuffle(ia, ia + N, randomness);
std::sort(ia, ia + M);
std::sort(ia + M, ia + N);
std::inplace_merge(Iter(ia), Iter(ia + M), Iter(ia + N));
if (N > 0) {
assert(ia[0] == 0);
assert(ia[N - 1] == static_cast<value_type>(N - 1));
assert(std::is_sorted(ia, ia + N));
}
delete[] ia;
}

template <class Iter>
void
test(unsigned N)
{
test_one<Iter>(N, 0);
test_one<Iter>(N, N/4);
test_one<Iter>(N, N/2);
test_one<Iter>(N, 3*N/4);
test_one<Iter>(N, N);
template <class Iter, class RandSrc>
TEST_CONSTEXPR_CXX26 void test(unsigned N, RandSrc& randomness) {
test_one<Iter>(N, 0, randomness);
test_one<Iter>(N, N / 4, randomness);
test_one<Iter>(N, N / 2, randomness);
test_one<Iter>(N, 3 * N / 4, randomness);
test_one<Iter>(N, N, randomness);
}

template <class Iter>
void
test()
{
test_one<Iter>(0, 0);
test_one<Iter>(1, 0);
test_one<Iter>(1, 1);
test_one<Iter>(2, 0);
test_one<Iter>(2, 1);
test_one<Iter>(2, 2);
test_one<Iter>(3, 0);
test_one<Iter>(3, 1);
test_one<Iter>(3, 2);
test_one<Iter>(3, 3);
test<Iter>(4);
test<Iter>(100);
test<Iter>(1000);
template <class Iter, class RandSrc>
TEST_CONSTEXPR_CXX26 void test(RandSrc& randomness) {
test_one<Iter>(0, 0, randomness);
test_one<Iter>(1, 0, randomness);
test_one<Iter>(1, 1, randomness);
test_one<Iter>(2, 0, randomness);
test_one<Iter>(2, 1, randomness);
test_one<Iter>(2, 2, randomness);
test_one<Iter>(3, 0, randomness);
test_one<Iter>(3, 1, randomness);
test_one<Iter>(3, 2, randomness);
test_one<Iter>(3, 3, randomness);
test<Iter>(4, randomness);
test<Iter>(50, randomness);
if (!TEST_IS_CONSTANT_EVALUATED) { // avoid exceeding the constant evaluation step limit
test<Iter>(100, randomness);
test<Iter>(1000, randomness);
}
}

int main(int, char**)
{
test<bidirectional_iterator<int*> >();
test<random_access_iterator<int*> >();
test<int*>();
TEST_CONSTEXPR_CXX26 bool test() {
support::simple_random_generator randomness;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, I think I would have favoured an approach where we get rid of the randomness in these tests instead, but I think this is acceptable and certainly has benefits. No action needed.


test<bidirectional_iterator<int*> >(randomness);
test<random_access_iterator<int*> >(randomness);
test<int*>(randomness);

#if TEST_STD_VER >= 11
test<bidirectional_iterator<S*> >();
test<random_access_iterator<S*> >();
test<S*>();
test<bidirectional_iterator<S*> >(randomness);
test<random_access_iterator<S*> >(randomness);
test<S*>(randomness);
#endif

return true;
}

int main(int, char**) {
test();
#if TEST_STD_VER >= 26
static_assert(test());
#endif // TEST_STD_VER >= 26

#if TEST_STD_VER >= 11 && !defined(TEST_HAS_NO_EXCEPTIONS)
{
std::vector<int> vec(150, 3);
getGlobalMemCounter()->throw_after = 0;
std::inplace_merge(vec.begin(), vec.begin() + 100, vec.end());
assert(std::all_of(vec.begin(), vec.end(), [](int i) { return i == 3; }));
}
if (!TEST_IS_CONSTANT_EVALUATED) {
std::vector<int> vec(150, 3);
getGlobalMemCounter()->throw_after = 0;
std::inplace_merge(vec.begin(), vec.begin() + 100, vec.end());
assert(std::all_of(vec.begin(), vec.end(), [](int i) { return i == 3; }));
}
#endif // TEST_STD_VER >= 11 && !defined(TEST_HAS_NO_EXCEPTIONS)

return 0;
Expand Down
Loading