This is an archive of the discontinued LLVM Phabricator instance.

[libc++] Implement ranges::ends_with
ClosedPublic

Authored by ZijunZhao on May 17 2023, 5:16 PM.

Details

Reviewers
ldionne
philnik
var-const
Group Reviewers
Restricted Project
Commits
rG0218ea4aaa54: [libc++] Implement ranges::ends_with

Diff Detail

Event Timeline

There are a very large number of changes, so older changes are hidden. Show Older Changes

make some format changes

ZijunZhao marked an inline comment as done.Jun 5 2023, 2:21 PM
ZijunZhao updated this revision to Diff 528623.Jun 5 2023, 4:33 PM

update all the robust_against_* tests but no need for robust_against_dangling one

ZijunZhao marked 2 inline comments as done.Jun 5 2023, 4:33 PM
ZijunZhao updated this revision to Diff 531574.Jun 14 2023, 5:01 PM
ZijunZhao marked 3 inline comments as done.

make some changes on tests

ZijunZhao updated this revision to Diff 533082.Jun 20 2023, 4:51 PM
ZijunZhao marked an inline comment as done.

replace ranges::drop_view with ranges::subrange

libcxx/test/std/algorithms/alg.nonmodifying/alg.ends_with/ranges.ends_with.pass.cpp
219

In line 252, forward_iterator_list is tested and include all input_iterators features. So input_iterators are tested?

var-const added inline comments.Jun 20 2023, 7:27 PM
libcxx/include/__algorithm/ranges_ends_with.h
60

Still seems not done?

63

Wrapping them in std::ref will probably solve the issue. equal has a bunch of optimizations based on the iterator type and it's likely we're skipping some logic by bypassing both equal and __equal and going straight to __equal_impl.

82

I would strongly advise to do so unless there's a compelling reason not to. It's better to avoid code duplication when possible in these cases.

libcxx/test/std/algorithms/alg.nonmodifying/alg.ends_with/ranges.ends_with.pass.cpp
66

I think these need to be reformatted (throughout the file).

77

Seems not done?

155

Looks like this test is duplicated (see line 139).

156

Seems not done?

170

How about:

The suffix occurs several time within the string, but not at the end.

?

216

Duplicated.

217

How about:

int a[] = {5, 1, 3, 2, 7};
int p[] = {-2, -7};
auto pred = [](int l, int r) { return l * -1 == r; };

I'm sorry for partially pushing my personal preference, but what I like about this is that a) it compares more than 1 element and b) the comparison is more specific (only one value would compare equal, whereas an infinite number of values would compare greater than). I also find that using the negative complement of a number is an easy transformation to read (no need to do any mental math).

219

It's kind of the opposite -- for an algorithm that supports input iterators, we need to make sure we don't accidentally end up relying on some features that are only available on forward iterators (or only on bidirectional iterators, etc.). In other words, we're checking that the algorithm only uses the very minimalistic interface provided by an input_iterator and nothing else. If we only test with forward_iterators, for example, we wouldn't be able to catch if the algorithm presumes that calling i++ on an iterator i returns a value (an input iterator can return anything, including void).

var-const added inline comments.Jun 20 2023, 7:46 PM
libcxx/test/std/algorithms/alg.nonmodifying/alg.ends_with/ranges.ends_with.pass.cpp
64

We need to also add one test to check the return type, something like:

{ // Check the return type
    int a[] = {1, 2, 3, 4, 5, 6};
    int p[] = {5, 6};
    auto whole = std::ranges::subrange(Iter1(a), Sent1(Iter1(a + 6)));
    auto suffix  = std::ranges::subrange(Iter2(p), Sent2(Iter2(p + 2)));
   
   {
      [[maybe_unused]] std::same_as<bool> decltype(auto) ret = std::ranges::ends_with(whole.begin(), whole.end(), suffix.begin(), suffix.end());
    }
    {
      [[maybe_unused]] std::same_as<bool> decltype(auto) ret = std::ranges::ends_with(whole, suffix);
    }
  }
ZijunZhao updated this revision to Diff 533787.Jun 22 2023, 2:40 PM
ZijunZhao marked 7 inline comments as done.

Fix some nits:

  1. delete duplication
  2. call ranges::equal in the implementation
  3. use decltype
  4. add some tests
libcxx/test/std/algorithms/alg.nonmodifying/alg.ends_with/ranges.ends_with.pass.cpp
64

okay I think the simple test is duplicated so I will make the simple test to check and use std::same_as<bool>

ZijunZhao updated this revision to Diff 533831.Jun 22 2023, 5:23 PM

delegate to the non-range overload of operator()

ZijunZhao updated this revision to Diff 534125.Jun 23 2023, 5:12 PM
ZijunZhao marked 5 inline comments as done.
  1. use ranges::advance() rather than std::advance()
  2. add two test cases: range consists of just one element and suffix consists of just one element
  3. remove the duplicated test case about predicate
libcxx/include/__algorithm/ranges_ends_with.h
82

okay! I add a new helper function like ranges_mismatch.h to simplify code!

var-const added inline comments.Jun 23 2023, 6:04 PM
libcxx/include/__algorithm/ranges_ends_with.h
44

This helper function doesn't need default arguments (since it's never invoked outside this file) and can take the predicate and the projections by reference (so there's no need to wrap the arguments passed to it in std::ref -- std::ref is only needed when passing those arguments to ranges::equal).

60

Friendly ping on this -- this sounds like a good optimization to implement.

ZijunZhao updated this revision to Diff 534813.Jun 26 2023, 6:41 PM
ZijunZhao marked 3 inline comments as done.
  1. Test input_iterators
  2. Update help function (will try to optimize later)
  3. some nits fixed
libcxx/include/__algorithm/ranges_ends_with.h
44

Wrapped by std::ref only when passing args to ranges::equal will cause ranges_robust_against_copying_comparators.pass.cpp fail.
So I only wrap them when passing args to __ends_with_fn_impl works.

ZijunZhao marked 2 inline comments as done.Jun 26 2023, 6:42 PM
ZijunZhao updated this revision to Diff 537540.Jul 5 2023, 4:15 PM
ZijunZhao marked 4 inline comments as done and 2 inline comments as done.
  1. Optimize when input is bidirectional iterator.
  2. Reformat test file
var-const added inline comments.Jul 11 2023, 5:47 PM
libcxx/include/__algorithm/ranges_ends_with.h
40

The helper function should take _Pred and _Proj1/_Proj2 by a reference, which would avoid the need for std::ref when delegating. Unlike the public functions which must have the same signature as in the standard, we can use references in the internal helper.

44

I mean that the helper function should take the arguments by reference, then wrap them in std::ref before passing to other algorithms. Basically, I'm suggesting to move the wrapping from the public function into the helper function.

48

Note: we only qualify the namespace on function calls (to avoid triggering ADL). There is no need to qualify class names, concept names, etc.

50

How about something like

if constexpr (bidirectional_iterator<_Iter1> && bidirectional_iterator<_Iter2>) {
  auto __rbegin1 = std::make_reverse_iterator(__first1);
  auto __rbegin2 = std::make_reverse_iterator(__first2);
  auto __rend1 = std::make_reverse_iterator(ranges::next(__first1, __last1));
  auto __rend2 = std::make_reverse_iterator(ranges::next(__first2, __last2));
  return ranges::starts_with(__rbegin1, __rend1, __rbegin2, __rend2, std::ref(__pred), std::ref(__proj1), std::ref(__proj2));
}

?

65

Why do we have to unwrap here? Can't we delegate that to equal?

72

std::ref should be here.

114

I think we're missing the case where the ranges are sized (meaning we can get the size in constant time) but the sentinels aren't, and the iterators aren't random-access. In other words, we miss the case when we have a way to get the sizes in constant time while we still have the range objects, but once we reduce them to iterators, getting the size becomes linear.

I took a stab at it and can suggest something like the following. There are many potential microoptimizations here (in particular if we consider all the cases where one of the ranges is sized and the other is not), but personally I think we need to strike the right balance between performance and code clarity. My current thinking is:

  1. If we can get the sizes from the ranges in constant time, that takes priority over everything else -- otherwise, we end up losing this bit of information down the line (in step 3). This also allows us to immediately filter out corner cases of empty ranges, or range2 being bigger than range1, in constant time;
  2. Otherwise, if we can iterate the ranges in reverse and have the end iterators, that's the next priority -- this avoids any unnecessary linear work for finding the sizes;
  3. Otherwise, calculate the sizes of both ranges (at worst two linear operations), advance the iterator on the first range (at worst linear), do the comparisons (linear). If any of these operations can be done in constant time, we get that without any extra code.

Pseudocode (I omitted the predicate and projections, as well as function attributes, for simplicity):

template <class _Iter1, class _Iter2>
bool __impl_bidirectional(_Iter1 __first1, _Iter1 __last1, _Iter2 __first2, _Iter2 __last2) {
  auto __rbegin1 = std::make_reverse_iterator(__first1);
  auto __rend1 = std::make_reverse_iterator(__last1);
  auto __rbegin2 = std::make_reverse_iterator(__first2);
  auto __rend2 = std::make_reverse_iterator(__last2);
  return ranges::starts_with(__rbegin1, __rend1, __rbegin2, __rend2);
}

template <class _Iter1, class _Sent1, class _Iter2, class _Sent2, class _Diff>
bool __impl_with_offset(_Iter1 __first1, _Sent1 __last1, _Iter2 __first2, _Sent2 __last2, _Diff __offset) {
  ranges::advance(__first1, __offset);
  return ranges::equal(__first1, __last1, __first2, __last2);
}

template <class _Iter1, class _Sent1, class _Iter2, class _Sent2>
bool __impl_with_sentinels(_Iter1 __first1, _Sent1 __last1, _Iter2 __first2, _Sent2 __last2) {
  if constexpr (bidirectional_iterator<_Sent1> && bidirectional_iterator<_Sent2>) {
    return __impl_bidirectional(__first1, __last1, __first2, __last2);

  } else {
    auto __n1 = ranges::distance(__first1, __last1);
    auto __n2 = ranges::distance(__first2, __last2);
    if (__n2 == 0) {
      return true;
    }
    if (__n2 > __n1) { // Also covers the case when `__n1` is `0`.
      return false;
    }

    return __impl_with_offset(__first1, __last1, __first2, __last2, __n1 - __n2);
}

// Public function that takes iterators
{
  return __impl_with_sentinels(__first1, __last1, __first2, __last2);
}

// Public function that takes ranges
{
  if constexpr (sized_range<_Range1> && sized_range<_Range2>) {
    auto __n1 = ranges::distance(__range1);
    auto __n2 = ranges::distance(__range2);
    if (__n2 == 0) {
      return true;
    }
    if (__n2 > __n1) { // Also covers the case when `__n1` is `0`.
      return false;
    }

    return __impl_with_offset(ranges::begin(__range1), ranges::end(__range1),
                              ranges::begin(__range2), ranges::end(__range2), __n1 - __n2);

  } else {
    return __impl_with_sentinels(ranges::begin(__range1), ranges::end(__range1),
                  ranges::begin(__range2), ranges::end(__range2));
  }
}
libcxx/test/std/algorithms/alg.nonmodifying/alg.ends_with/ranges.ends_with.pass.cpp
256

You might want to add a comment to this branch and the next one that this is to test the (forward_iterator<_Iter1> || sized_sentinel_for<_Sent1, _Iter1>) condition.

ZijunZhao marked 2 inline comments as done.
  1. If we can get the size of input directly, get it, then compare the sizes and return the result in some cases. Otherwise, go to step2.
  2. If the input is bidirectional, go to __bidirectional_impl() directly. If not, go to step 3
  3. Call ranges::equal() to get the result
ZijunZhao added inline comments.Jul 21 2023, 10:35 PM
libcxx/include/__algorithm/ranges_ends_with.h
72

yes, I tried to pass std::ref in ranges::equal() and ranges::start() only and copy tests failed. And tries to remove std::ref when passing to __ends_with_fn_impl() and still failed the tests.

ZijunZhao marked an inline comment as done.Aug 16 2023, 4:59 PM
ZijunZhao updated this revision to Diff 551678.Aug 18 2023, 5:24 PM

Pass by referrence

ZijunZhao marked 4 inline comments as done.Aug 18 2023, 5:25 PM
ZijunZhao updated this revision to Diff 551685.Aug 18 2023, 5:49 PM
ZijunZhao marked an inline comment as done.

add a comment to make code more clear

libcxx/include/__algorithm/ranges_ends_with.h
48

Do I need to add a comment here?

philnik requested changes to this revision.Aug 18 2023, 6:08 PM

Looks pretty good, but I'd like to get the ranges::equal optimization in again, since it's probably quite significant.

libcxx/include/__algorithm/ranges_ends_with.h
37

It looks like we've dropped the ranges::equal optimization again?

65–83

Just to make it a bit leaner to compile.

148–149

To make it obvious that it's constant time and avoid a few instantiations.

165–173
This revision now requires changes to proceed.Aug 18 2023, 6:08 PM
AMP999 added a subscriber: AMP999.Aug 19 2023, 7:16 AM
AMP999 added inline comments.
libcxx/include/__algorithm/ranges_ends_with.h
159

I don't think these std::moves are needed since ranges::begin() already return prvalue.

ZijunZhao updated this revision to Diff 552125.Aug 21 2023, 1:55 PM
ZijunZhao marked 2 inline comments as done.

Update else to make it a bit leaner to compile.

ZijunZhao updated this revision to Diff 552127.Aug 21 2023, 2:12 PM
ZijunZhao marked 2 inline comments as done.
  1. use ranges::size() instead of ranges::distance to get the size of an array at constant time
  2. remove std::move for all input which call ranges algorithms.
ZijunZhao updated this revision to Diff 552921.Aug 23 2023, 4:31 PM
ZijunZhao marked an inline comment as done.

Get ranges::equal optimization back when the iterator is random access iterator, and add a benchmark test to verify the optimization works.

ldionne added inline comments.Aug 25 2023, 2:11 PM
libcxx/test/std/algorithms/alg.nonmodifying/alg.ends_with/ranges.ends_with.pass.cpp
275

Instead, we should add a benchmark under <monorepo>/libcxx/benchmarks and post the results of that benchmark here in this review. I know this isn't an automated test, but the test as-is risks being flaky. Let's leave the benchmarks in libcxx/benchmarks and we can figure out a way to track performance through time (and potentially compare performance of different operations?) more generally.

AMP999 removed a subscriber: AMP999.Aug 25 2023, 2:39 PM
var-const requested changes to this revision.Aug 30 2023, 5:18 PM

Thanks a lot, this is getting very close to LGTM. The implementation looks good (just a few formatting nitpicks), just a few minor things in tests to address plus the benchmark needs to be reworked. My sincere apologies for the very slow review (mostly due to the upcoming LLVM 17 release).

libcxx/include/__algorithm/ranges_ends_with.h
40

How about naming this function __ends_with_fn_impl_bidirectional, just for consistency with the other helpers?

68

Nit: please add a blank line before this line.

74

Nit: please add a blank line after this line.

105

Nit: can you add spaces before and after the &&? Or just clang-format these lines. If it's clang-format that produces this formatting, then never mind (although I'd be surprised).

105

Nit: the parentheses around !std::random_access_iterator<_Sent1> are unnecessary (same for the _Sent2 check).

106

Nit: std::move the iterators here as well?

118

Nit: please add a blank line after this line (to separate different functions from each other).

168

Nit: please add a blank line before this line.

libcxx/test/std/algorithms/alg.nonmodifying/alg.ends_with/ranges.ends_with.pass.cpp
43

Nit: I think we need one successful test case for each number of template arguments, so here we need to add:

static_assert(!HasEndsWithIt<int*, int*>);

(this is to "test the test", so to speak)

52

Nit: Range1 and Range2 should be forwarding references, and in the body of the concept, range1 and range2 should be forwarded. That way, it would be more similar to how the function is actually invoked.

59

Here we also need to have a successful test case for two template arguments.

60

Optional: IMO it's not necessary to test out every single not-forward-range we have, just a couple is enough (e.g. I would leave just ForwardRangeNotDerivedFrom and ForwardRangeNotSentinelSemiregular).

235

Nit: formatting needs fixing.

256

I think this should be random_access_iterator list, otherwise none of the iterator types in the list are a forward iterator so the if constexpr (std::forward_iterator...) below will never be taken.

275

Nit: s/benmark/benchmark/ (here and below).

275

+1, benchmarks should be under the benchmarks folder and use the infrastructure we're currently using (that is, Google Benchmark).

Also, we shouldn't be benchmarking the implementation details like this, partially because it's easy for the benchmark and the actual implementation to get out of sync. Rather, you can use the same input vectors for different test cases but wrap their iterators into our helper iterators, then call ends_with using the wrappers:

std::vector<int> a = /*some initializer*/;
std::vector<int> p = /*some initializer*/;

{
  std::random_access_iterator<int*> begin1(a.begin());
  std::random_access_iterator<int*> end1(a.end());
  std::random_access_iterator<int*> begin2(p.begin());
  std::random_access_iterator<int*> end2(p.end());
  std::ranges::ends_with(begin1, end1, begin2, end2);
  // ...
}
{
  std::bidirectional_iterator<int*> begin1(a.begin());
  std::bidirectional_iterator <int*> end1(a.end());
  std::bidirectional_iterator <int*> begin2(p.begin());
  std::bidirectional_iterator <int*> end2(p.end());
  std::ranges::ends_with(begin1, end1, begin2, end2);
  // ...
}

You can even test the sized optimization using sized_sentinel. Like Louis mentioned above, we unfortunately don't have a way to test for performance regressions currently, and we shouldn't try to solve that problem in this patch. Rather, it would be great if you could run the new benchmarks locally, make sure the numbers are as expected (i.e., random access iterators are faster than bidirectional iterators, etc.), then post the results in this review.

276

This should be just std::vector.

278

Nit: formatting (should be a space after for, i.e., for ().

284

Names used in tests don't have to be uglified with leading __ underscores (in fact, they shouldn't be since these are technically reserved names for the library. Even though the test is testing the library, the test itself isn't a part of the library).

289

Since these are the default values, I think they should be omitted (the predicate and the projections, that is).

This revision now requires changes to proceed.Aug 30 2023, 5:18 PM
ZijunZhao updated this revision to Diff 554883.Aug 30 2023, 6:19 PM
ZijunZhao marked 12 inline comments as done.
  1. reformat
  2. rename helper function
  3. add std::move
ZijunZhao updated this revision to Diff 554885.Aug 30 2023, 6:34 PM
ZijunZhao marked an inline comment as done.

reformat

ZijunZhao marked an inline comment as done.Aug 31 2023, 10:35 PM
ZijunZhao added inline comments.
libcxx/test/std/algorithms/alg.nonmodifying/alg.ends_with/ranges.ends_with.pass.cpp
256

I think cpp20_input_iterator_list already contains random_access_iterator?
Why I think so is because from libcxx/test/support/test_iterators.h

using random_access_iterator_list =
    type_list<Ptr,
#if TEST_STD_VER >= 20
              contiguous_iterator<Ptr>,
#endif
              random_access_iterator<Ptr> >;

template <class Ptr>
using bidirectional_iterator_list =
    concatenate_t<random_access_iterator_list<Ptr>, type_list<bidirectional_iterator<Ptr> > >;

template <class Ptr>
using forward_iterator_list = concatenate_t<bidirectional_iterator_list<Ptr>, type_list<forward_iterator<Ptr> > >;

template <class Ptr>
using cpp17_input_iterator_list = concatenate_t<forward_iterator_list<Ptr>, type_list<cpp17_input_iterator<Ptr> > >;

#if TEST_STD_VER >= 20
template <class Ptr>
using cpp20_input_iterator_list =
    concatenate_t<forward_iterator_list<Ptr>, type_list<cpp20_input_iterator<Ptr>, cpp17_input_iterator<Ptr>>>;
#endif
ZijunZhao marked an inline comment as done.Aug 31 2023, 10:35 PM
ZijunZhao updated this revision to Diff 555941.Sep 5 2023, 2:54 PM
ZijunZhao marked 2 inline comments as done.

Update HasEndsWithR test

ZijunZhao marked an inline comment as done.Sep 5 2023, 2:55 PM
var-const added inline comments.Sep 5 2023, 3:11 PM
libcxx/test/std/algorithms/alg.nonmodifying/alg.ends_with/ranges.ends_with.pass.cpp
53

Nit: you would also need to call std::forward<Range1>(range1) here (same for the other argument).

275

Sorry, rereading this comment, I mean random_access_iterator and bidirectional_iterator from our test_iterators.h header. Please ignore the std:: qualification, I copy-pasted it accidentally. So basically

std::vector<int> a = /*some initializer*/;
std::vector<int> p = /*some initializer*/;

{
  random_access_iterator<int*> begin1(a.begin());
  random_access_iterator<int*> end1(a.end());
  random_access_iterator<int*> begin2(p.begin());
  random_access_iterator<int*> end2(p.end());
  std::ranges::ends_with(begin1, end1, begin2, end2);
  // ...
}
{
  bidirectional_iterator<int*> begin1(a.begin());
  bidirectional_iterator<int*> end1(a.end());
  bidirectional_iterator<int*> begin2(p.begin());
  bidirectional_iterator<int*> end2(p.end());
  std::ranges::ends_with(begin1, end1, begin2, end2);
  // ...
}
ZijunZhao updated this revision to Diff 555959.Sep 5 2023, 6:56 PM

Update benchmarks test

ZijunZhao marked 3 inline comments as done.Sep 5 2023, 6:57 PM
ZijunZhao marked an inline comment as done.
ZijunZhao updated this revision to Diff 556086.Sep 6 2023, 2:44 PM
ZijunZhao marked an inline comment as done.

Update test

var-const added inline comments.Sep 7 2023, 5:43 PM
libcxx/benchmarks/algorithms/ranges_ends_with.bench.cpp
15

I tried out the benchmarks locally. After applying the fixes outlined above, I noticed a couple of interesting things:

  1. Random access iterators end up being a little slower than bidirectional iterators, at least in my environment. This is because std::__equal_impl ends up being a little slower than std::ranges::__mismatch::__fn::__go for the same arguments. It looks like the combination of calling advance on __first1 (even though in this case it's a no-op) with the fact that equal checks __first1 == __last1 in the end inhibits some optimization. I'm reluctant to try to fix this, though, at least in this patch -- the difference is small and isn't really specific to ends_with; it might also be dependent on the compiler.
  1. equal contains a powerful memcmp optimization for contiguous iterators but it doesn't get triggered currently. That's because ends_with wraps the predicate and the projections in a reference_wrapper, and the optimization only kicks in if (simplified):
__is_trivial_equality_predicate<_Pred, _Tp, _Up>::value && __is_identity<_Proj1>::value && __is_identity<_Proj2>::value

__is_trivial_equality_predicate recognizes ranges::equal_to as trivial, but not reference_wrapper<ranges::equal_to>, similarly for __is_identity.

There are a few potential solutions:

  1. Modify these traits to recognize reference_wrapper.
  2. Call an internal version of ranges::equal that takes functors by reference (needs to be written) or call std::__equal directly. It would also be necessary to duplicate the __unwrap_iter and __unwrap_range logic that's currently in ranges::equal.
  3. Stop providing the guarantee that we never copy functors in algorithms.

I'm currently leaning towards option 1. @philnik Nikolas, what do you think?

22

Sorry, I miswrote the suggestion above, this should be

#include "test_iterators.h"

// ...

auto begin1 = random_access_iterator(a.begin());
// ...

std::random_access_iterator is a concept and as such it evaluates to a boolean. In other words, begin1, end1, begin2 and end2 end up being boolean variables, then we create zero- or one-element containers from those booleans (because they implicitly convert to integers, unfortunately), which means we ignore the size of state.range() generated by benchmark and always measure the same (meaningless) thing.

26

What we want to do is to test how the implementation of the algorithm treats different iterator categories, but at the same time we want to use the same underlying container type so that it's not a factor in our measurement (otherwise, if we compare e.g. a vector against a list, it's impossible to say how much of the performance difference is due to random-access iterator optimizations in ends_with and how much is due to the fact that vector is much more cache-friendly). Since we can make the iterator category more limited (e.g. turn a bidirectional iterator into a forward iterator) but not vice versa, that means we need to use a container with the most powerful iterator category -- so it would have to be a vector, an array or a built-in array.

If we use a container, wrap its iterators but then create a new container from those iterators, it negates the idea because the new container would have the same iterator category as before. E.g. if we create a list, wrap its (bidirectional) iterators into forward iterators, then create a new list from those forward iterators, the new list once again would have bidirectional iterators, completely negating our wrapping. In other words, you should just do

benchmark::DoNotOptimize(std::ranges::ends_with(begin1, end1, begin2, end2));
40

We would also want to test with contiguous_iterators and forward_iterators.

philnik added inline comments.Sep 7 2023, 8:46 PM
libcxx/benchmarks/algorithms/ranges_ends_with.bench.cpp
15

I think both (1) and (3) make sense. (1) Seems like a nice generalizations, but I doubt it will make much of a difference 99% of the time, and (3) seems like it simplifies some things quite a bit. I also have the suspicion that all the references in the algorithms might inhibit optimizations, but I didn't test that yet. I also don't expect most predicates and projections to be non-trivial, making this an optimization that only applies very rarely, and potentially a pessimization in some cases (although that's just conjecture on my part).

ZijunZhao updated this revision to Diff 556508.Sep 11 2023, 5:07 PM
ZijunZhao marked an inline comment as done.

Update benchmark tests

ZijunZhao marked 2 inline comments as done.Sep 11 2023, 5:08 PM
ZijunZhao marked an inline comment as done.
var-const accepted this revision.Sep 12 2023, 11:02 PM

Thank you for working on this and addressing all the feedback! LGTM with a green CI (let me know if you need any help with CI failures).

I don't know if you have commit permissions -- if not, I can help merge the patch (please provide the name and the email address to use for the author field in that case).

libcxx/benchmarks/algorithms/ranges_ends_with.bench.cpp
15

@philnik Thanks!

@ZijunZhao As discussed offline, let's implement (1) in a follow-up.

15

Nit: I don't think this header is used.

43

Nit: please add a blank line after this line.

68

Nit: extraneous blank line (here and in some other functions in this file).

95

Nit: please add a blank line after this line for consistency with other functions in this file.

ZijunZhao marked 7 inline comments as done.
  1. add some headers in ranges_ends_with.h
  2. fix nits: add/remove some blank lines
  3. remove useless headers in ranges.ends_with.pass.cpp
ZijunZhao marked an inline comment as done.Sep 13 2023, 12:23 AM
ZijunZhao updated this revision to Diff 556738.Sep 13 2023, 6:03 PM

remove redundant headers to avoid transitive includes

Remove unused header files

Modify algorithm.inc to pass the presubmit

ZijunZhao updated this revision to Diff 556882.Sep 15 2023, 3:13 PM

Update tests

ZijunZhao updated this revision to Diff 556883.Sep 15 2023, 3:17 PM

change #if TEST_STD_VER > 20 to #if TEST_STD_VER >= 23

ZijunZhao updated this revision to Diff 556887.Sep 15 2023, 4:41 PM

Correct the #if condition for ranges::starts_with() and ranges::ends_with()

Update algorithm.inc to pass prechecks

This revision was not accepted when it landed; it landed in state Needs Review.Sep 18 2023, 11:59 AM
This revision was automatically updated to reflect the committed changes.