Implement ranges::min_element
Details
- Reviewers
• Quuxplusone ldionne Mordante var-const - Group Reviewers
Restricted Project - Commits
- rG3b470d1ce992: [libc++][ranges] Implement ranges::min_element
Diff Detail
- Repository
- rG LLVM Github Monorepo
Unit Tests
Event Timeline
libcxx/test/std/algorithms/alg.sorting/alg.min.max/ranges.min_element.pass.cpp | ||
---|---|---|
120 | Is there a reason the tests are disabled? |
libcxx/test/std/algorithms/alg.sorting/alg.min.max/ranges.min_element.pass.cpp | ||
---|---|---|
120 | Just forgot to re-enable it. Thanks for noting. |
libcxx/include/__algorithm/ranges_min_element.h | ||
---|---|---|
50 | This makes copies of __comp and __proj. At the very least we should std::move them; but I think we should also give serious consideration to just taking them by forwarding reference to begin with. Speaking of how scary it is to mess with overload sets... what's the mandated behavior for a call like std::ranges::min_element(42) — does it need to SFINAE away quietly? If so, we should have a test for that. | |
libcxx/include/algorithm | ||
23 | Nit: lowercase since C++20 to match line 23 (and generally) | |
libcxx/include/module.modulemap | ||
283 | Typo here. I'll update lint_modulemap.sh.py. :) | |
libcxx/test/std/algorithms/alg.sorting/alg.min.max/ranges.min_element.pass.cpp | ||
31 | I see zero reason for this test to use randomness. We just need to check that it works when the first element is the min; when the last element is the min; when some middle element is the min; when there's a tie for min; when the range's size is 1; when the range's size is 0; when the range's value type is immobile (i.e. we shouldn't ever be copying/moving elements). |
- Rebased
- Put implementation into other function
- Move Immobile declaration out of function
The code looks great at this point! Significant comments on the test.
libcxx/include/__algorithm/ranges_min_element.h | ||
---|---|---|
35 | Remove blank line. | |
44 | bool(...) is unnecessary here, because the invoke_result_t of a predicate is guaranteed to be boolean-testable. If someone thinks the cast looks nice for some reason, I won't complain too much; but IMHO you should eliminate the cast as unnecessary. | |
libcxx/test/std/algorithms/alg.sorting/alg.min.max/ranges.min_element.pass.cpp | ||
33–34 | Please either name this concept HasMinElement, or just use | |
71 | Moot style point: Stylistically I prefer (x , ...) over (x, ...) for the same reason I prefer (x + ...) over (x+ ...). | |
95–101 | See, this is a great test, because it's simple enough that I think I see what it's doing. ;) { int a[] = {2, 3, 1, 3, 2}; int *ret = std::ranges::min_element(a, std::greater()); assert(ret == a + 1); } { const int a[] = {2, 3, 1, 3, 2}; const int *ret = std::ranges::min_element(a, std::less()); assert(ret == a + 2); } or something like that. I don't think we need sentinel_wrapper or subrange (or std::ranges::greater) if the goal is to test the comparator codepath specifically. | |
107 | Nice example of a call where the answer would be different if the predicate weren't there! int a[] = {2, 1, 3}; int *ret = std::ranges::min_element(a, std::less<int*>(), [](int& i) { return &i; }); assert(ret == a); | |
128 | Moot: , 0 is kind of a weird corner case; I'd make this , 10 or , 42. constexpr void test_dangling() { int a[] = {2, 1, 3}; int compares = 0; int projections = 0; auto comparator = [&](int a, int b) { compares += 1; return a < b; }; auto projection = [&](int a) { projections += 1; return a; }; std::same_as<std::ranges::dangling> auto r = std::ranges::min_element(r, comparator, projection); assert(compares == 2); assert(projections == 4); } |
libcxx/test/std/algorithms/alg.sorting/alg.min.max/ranges.min_element.pass.cpp | ||
---|---|---|
71 | The reason was that I didn't want to run test_range multiple times, and having a single fold expression doesn't strike me as very much meta-programming. | |
95–101 | Why do you want to test std::less? It's tested literally every where else. |
Let's fix that variadic test function. It's cheap and easy to fix.
libcxx/test/std/algorithms/alg.sorting/alg.min.max/ranges.min_element.pass.cpp | ||
---|---|---|
71 |
I would have said the same thing! ;) I write fun(a, b) and {1, 2} and so on because each of those commas represents the comma separator. But when I use the comma operator, at least in a fold-expression context where the reader might casually mistake it for the comma separator, I'll call out that unusual usage by formatting it like an operator. | |
75 | ||
95–101 | Well, in general TDD terms, testing two different comparators "proves" that the implementation isn't just "if there's a comparator object then reverse all the comparisons" or something. But I don't mind just testing std::greater. | |
107 | Here please use std::less<int*>() with the specific type int*, because I want to verify that the comparator doesn't need to be callable with the original element type — we want it to be callable (and called) only with the projected type. | |
128 | You can remove line 128 (the static_assert) now, which will allow you to remove #include <array> (and eliminate the weirdness of std::array<T, 0>). | |
150–153 |
libcxx/test/std/algorithms/alg.sorting/alg.min.max/ranges.min_element.pass.cpp | ||
---|---|---|
128 | I'm still using std::array in line 140. |
libcxx/test/std/algorithms/alg.sorting/alg.min.max/ranges.min_element.pass.cpp | ||
---|---|---|
72 | I would like to see us test some ranges that are not std::array. Given the implementation, I guess it's not likely that anything could go wrong; but still, you could get vastly more mileage out of this test by making it something like template<class It> constexpr void test_one_case(std::initializer_list<int> a, int expected_idx) { int *first = a.begin(); int *last = a.end(); { std::same_as<It> auto it = std::ranges::min_element(It(first), It(last)); assert(base(it) == first + expected_idx); } { using Sent = std::sentinel_wrapper<It>; std::same_as<It> auto it = std::ranges::min_element(It(first), Sent(It(last))); assert(base(it) == first + expected_idx); } { auto r = std::ranges::subrange(It(first), It(last)); std::same_as<It> auto it = std::ranges::min_element(r); assert(base(it) == first + expected_idx); } { using Sent = std::sentinel_wrapper<It>; auto r = std::ranges::subrange(It(first), Sent(It(last))); std::same_as<It> auto it = std::ranges::min_element(r); assert(base(it) == first + expected_idx); } } template<class It> constexpr void test_cases() test_one_case<It>({}, 0); test_one_case<It>({1}, 0); test_one_case<It>({1, 2, 1}, 0); test_one_case<It>({2, 1, 3}, 1); test_one_case<It>({3, 2, 1}, 2); } ~~~ test_cases<forward_iterator<int*>>(); test_cases<bidirectional_iterator<int*>>(); [etc.] | |
75 | Throughout: s/Iters/Iter/ (or even It). |
LGTM modulo one suggestion.
libcxx/test/std/algorithms/alg.sorting/alg.min.max/ranges.min_element.pass.cpp | ||
---|---|---|
108 | For reviewing it would be nice to also validate the value assert(*ret == x); in all these tests. |
Remove blank line.