Skip to content

[libc++] Implement ranges::iota #68494

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 50 commits into from
Apr 5, 2025
Merged

Conversation

jamesETsmith
Copy link
Contributor

@jamesETsmith jamesETsmith commented Oct 7, 2023

Overview

As a disclaimer, this is my first PR to LLVM and while I've tried to ensure I've followed the LLVM and libc++ contributing guidelines, there's probably a good chance I missed something. If I have, just let me know and I'll try to correct it as soon as I can.

This PR implements std::ranges::iota and std::ranges::out_value_result outlined in P2440r1.

As outlined in the paper above, I've:

  • Implemented out_value_result and added to <algorithm>
  • Added out_value_result, iota_result, and two overloads of iota to std::ranges in <numeric>
  • Updated the version macro __cpp_lib_ranges_iota in <version>

I've also added tests for ranges::iota and ranges::out_value_result. Lastly, I added those structs to the appropriate module files.

Partially implements #105184

EDIT: Forgot to mention in the original post, thanks to @hawkinsw for taking a look at a preliminary version of this PR!

TODOs

  • Updating the range status doc
  • Ensure all comments from https://reviews.llvm.org/D121436 are addressed here
  • EDIT (I'll do this in a separate PR). I'm open to implementing the rest of P2440r1 (ranges::shift_left and ranges::shift_right) if that's ok, I just wanted to get feedback on ranges::iota first
  • I've been having trouble building the modules locally and want to make sure that's working properly

Closes: #134060

@jamesETsmith jamesETsmith requested a review from a team as a code owner October 7, 2023 18:51
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Oct 7, 2023
Copy link
Contributor

@philnik777 philnik777 left a comment

Choose a reason for hiding this comment

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

I haven't looked at the tests yet. The implementation itself looks mostly good.

@@ -0,0 +1,124 @@
//===----------------------------------------------------------------------===//
Copy link
Contributor

Choose a reason for hiding this comment

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

I've started working on this in https://reviews.llvm.org/D121436 (and didn't really work on it because nobody knew what it was for). You should probably make sure that any comments there are addressed here and add any additional tests from there.

struct __iota_fn {
template <input_or_output_iterator _Out, sentinel_for<_Out> _Sent, weakly_incrementable _Tp>
requires indirectly_writable<_Out, const _Tp&>
constexpr iota_result<_Out, _Tp> operator()(_Out __first, _Sent __last, _Tp __value) const {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
constexpr iota_result<_Out, _Tp> operator()(_Out __first, _Sent __last, _Tp __value) const {
static constexpr iota_result<_Out, _Tp> operator()(_Out __first, _Sent __last, _Tp __value) {

same below

Copy link
Contributor Author

@jamesETsmith jamesETsmith Oct 25, 2023

Choose a reason for hiding this comment

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

@philnik777, I'm happy to make these changes, but could you explain why static is more appropriate than const here?

As an alternative, after I've added the helper function, I could make that static and leave the public functions as const like it says in the original paper. I stumbled on this strategy (it's used by ranges::for_each and ranges::generate at least) while looking at how other libcxx helper functions were structured.

EDIT: I realized it would probably be easier to just implement this second strategy (in a455f42) and see what you think.

Copy link
Contributor

Choose a reason for hiding this comment

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

static operator() has been introduced in C++23, so we can't (currently) use it for C++20 algorithms. Otherwise we would also use it there. The simple reason to use it is that we don't care about the object and the static version has slightly better code gen (https://godbolt.org/z/8E1f77q6f).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah I see, I wasn't aware that static operator() was introduced in C++23. That clears things up, thanks for the example.


template <weakly_incrementable _Tp, ranges::output_range<const _Tp&> _Range>
constexpr iota_result<ranges::borrowed_iterator_t<_Range>, _Tp> operator()(_Range&& __r, _Tp __value) const {
return (*this)(ranges::begin(__r), ranges::end(__r), std::move(__value));
Copy link
Contributor

Choose a reason for hiding this comment

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

You'll probably run into problems with this when updating the robust_against_* tests. You should move the algorithm itself into a helper.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't see why this would cause problems?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry I've been slow on this. @philnik777 do you have any suggestions/comments about this?

Copy link
Member

Choose a reason for hiding this comment

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

We have "robust" tests to make sure we're not copying or moving comparators and projections, but I don't think we do that for "values" as is the case here. Whether it's an omission or not is a separate question, but as it stands, I think the original form of this code (forwarding directly from the ranges overload to the iterator overload) should work -- can you please check that, @jamesETsmith?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I thought I had rechecked that the original implementation had caused problems with the robust_against_* tests, but after checking again today, it doesn't seem to cause any problems.

As a result, I've reverted to the original strategy at @cjdb and @var-const's suggestion.

@jamesETsmith
Copy link
Contributor Author

Thanks for taking a look @philnik777! I'll try to address your comments in the next couple of days and let you know if I have any questions.

@jamesETsmith
Copy link
Contributor Author

Sorry for the delay, just circling back to this. @philnik777 should I update the robust_against_* tests in both test/std/algorithms and test/libcxx/algorithms/?

Probably a simple follow-up question, but you're interested in adding ranges::iota to the ranges_robust_against_* right? Or should it be added to the robust_against_* tests?

@philnik777
Copy link
Contributor

Yes, it should be the ranges_robust_against_* tests in both test/libcxx and test/std.

@github-actions
Copy link

github-actions bot commented Oct 26, 2023

✅ With the latest revision this PR passed the C/C++ code formatter.

@jamesETsmith
Copy link
Contributor Author

@philnik777, I've added tests for ranges::iota to several of the ranges_robust_against_* tests where I thought it was appropriate. Below is a breakdown (mostly for my own tracking) of what I've done and what I'm still sorting through (ranges_robust_against_proxy_iterators). If you see anything that I missed or got wrong, just let me know.

  • libcxx/test/std/algorithms
    • libcxx/test/std/algorithms/ranges_result_alias_declarations.compile.pass.cpp
    • libcxx/test/std/algorithms/ranges_robust_against_dangling.pass.cpp
    • libcxx/test/std/algorithms/ranges_robust_against_differing_projections.pass.cpp
      • I don't think we need this one, since we don't use projections
    • libcxx/test/std/algorithms/ranges_robust_against_nonbool_predicates.pass.cpp
      • I don't think we need this one since we don't use predicates
    • libcxx/test/std/algorithms/ranges_robust_against_omitting_invoke.pass.cpp
      • I don't think we need this one because we don't use invoke
    • libcxx/test/std/algorithms/ranges_robust_against_proxy_iterators.pass.cpp
      • This one is giving me trouble bc because 'Proxy<int &>' does not satisfy 'weakly_incrementable'
  • libcxx/test/libcxx/
    • libcxx/test/libcxx/algorithms/ranges_robust_against_copying_comparators.pass.cpp
      • I don't think we need this since we don't use comparators
    • libcxx/test/libcxx/algorithms/ranges_robust_against_copying_projections.pass.cpp
      • I don't think we need this since we don't use projections

@philnik777
Copy link
Contributor

philnik777 commented Oct 27, 2023

Why does the proxy not satisfy weakly_incrementable? Is it maybe just missing some member functions? Otherwise the list looks reasonable.

@philnik777 philnik777 added the ranges Issues related to `<ranges>` label Oct 27, 2023
@jamesETsmith
Copy link
Contributor Author

@philnik777, I think you're right that Proxy is missing a few member functions (and type aliases). Here's a minimal example of my additions to it: https://godbolt.org/z/8T9eqjjEj. If that seems reasonable, I'll update test_iterators.h.

@philnik777
Copy link
Contributor

You should make the operators conditionally available based on whether the underlying type has them. The rest looks good.

@jamesETsmith
Copy link
Contributor Author

jamesETsmith commented Oct 27, 2023

Making the operations conditionals was pretty straightforward, but using difference_type = std::iter_difference_t<T> is causing problems because it requires that all T types implement T::difference_type which I think is too strong a requirement. For example, this will cause Proxy<MoveOnly> to crash because MoveOnly doesn't implement difference_type.

My gut reaction was to try and make that alias require a concept, but the more I try to get something like a "conditional using-declaration" to work the more it feels like that isn't allowed and I'm thinking about this the wrong way. Do you have any suggestions @philnik777?

Here's an updated minimal example showing the crash that I'm talking about: https://godbolt.org/z/KocGjhPhz

@philnik777
Copy link
Contributor

You can add a base class like this: https://godbolt.org/z/ccG7jcqee

@jamesETsmith
Copy link
Contributor Author

jamesETsmith commented Oct 27, 2023

That's much cleaner than what I was coming up with, thanks! I'll give that a try

@strega-nil
Copy link
Contributor

@strega-nil, thanks for taking a look. You're welcome to merge it if you like. Do you want me to fix these merge conflicts or were you planning to do that? I'm happy to fix them, but don't want to duplicate that work if you've already started it locally.

I have not started it, it'd be great if you could do a merge commit 😄

@jamesETsmith
Copy link
Contributor Author

@strega-nil, ok I'll do that now

@jamesETsmith
Copy link
Contributor Author

@strega-nil, I think this is good to go from my end unless people have further comments.

… version.version.compile tests rather than generating it with the python script so the formatting was off
@jamesETsmith
Copy link
Contributor Author

I forgot (again) to update libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp using the script so the formatting of that file was incorrect. Sorry about that

@strega-nil
Copy link
Contributor

I'll take a look at this tomorrow 😃

Copy link
Contributor

@strega-nil strega-nil left a comment

Choose a reason for hiding this comment

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

This looks great! Just some minor nits, but other than that almost ready to merge 😄

@@ -2,4 +2,5 @@ Standard,Name,Assignee,CL,Status
C++23,`ranges::to <https://wg21.link/P1206R7>`_,Konstantin Varlamov,`D142335 <https://reviews.llvm.org/D142335>`_,Complete
C++23,`Pipe support for user-defined range adaptors <https://wg21.link/P2387R3>`_,Unassigned,No patch yet,Not started
C++23,`Formatting Ranges <https://wg21.link/P2286R8>`_,Mark de Wever,Various,Complete
C++23, `ranges::iota <https://wg21.link/P2440R1>`_, James E T Smith, `PR68494 <https://github.com/llvm/llvm-project/pull/68494>`_, In Progress
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't believe I'd call this a "major ranges feature"; this is an important ranges feature, but isn't quite as large as the other things on this list; I'd remove this change.

@@ -100,6 +101,11 @@ constexpr void run_tests() {
test(std::ranges::search, in, in2);
test(std::ranges::search_n, in, count, x);
test(std::ranges::find_end, in, in2);
#if TEST_STD_VER >= 23
if constexpr (std::copyable<T>) {
Copy link
Contributor

Choose a reason for hiding this comment

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

We could also do if constexpr (weakly_incrementable<T>) { test(std::ranges::iota, in, T{}); }?

@jamesETsmith
Copy link
Contributor Author

@strega-nil, thanks again for looking this over. I hope I've addressed all your comments with b4008ad (with the exception of this one, which is I'm waiting on until we reach a resolution)

Copy link
Contributor

@strega-nil strega-nil left a comment

Choose a reason for hiding this comment

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

Thanks for finishing this up! Now we just need one more reviewer, and you can merge 😄

@jamesETsmith
Copy link
Contributor Author

Thanks again @strega-nil!

@var-const, @cjdb, and @philnik777 could you take another look at this PR and let me know if I've addressed your comments and if you have any other changes you'd like to see?

Copy link
Contributor

@cjdb cjdb left a comment

Choose a reason for hiding this comment

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

Nearly there! Thanks for you patience :)

// which we need in certain situations. For example when we want
// std::weakly_incrementable<Proxy<T>> to be true.
template <class T>
struct ProxyDiffTBase {
Copy link
Contributor

Choose a reason for hiding this comment

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

As a rule of thumb, I don't mind duplication in tests so that things are known to be correct on inspection. Abstraction is fine, but only if it doesn't cast doubt on our confidence that the test is correct.

I think in this case, I'd prefer a proxy_with_difference_type and proxy_missing_difference_type. That might be deserving of its own PR, and before this one is merged so you can use it.

template <class T>
struct ProxyDiffTBase {
// Add default `operator<=>` so that the derived type, Proxy, can also use the default `operator<=>`
friend constexpr auto operator<=>(const ProxyDiffTBase&, const ProxyDiffTBase&) = default;
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: pass by value (same elsewhere for types that you wouldn't ordinarily pass by ref-to-const).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree it seems a bit unnecessary to pass by ref-to-const here, but I was basing these on the existing Proxy<T> which uses ref-to-const

@Zingam
Copy link
Contributor

Zingam commented Apr 1, 2025

@jamesETsmith I think it makes sense to create a subissues in #105184 so you can associate this PR with it.

Copy link
Contributor

@philnik777 philnik777 left a comment

Choose a reason for hiding this comment

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

Thanks for the work! LGTM. I don't think you need to wait for @var-const. He's not been doing much work upstream recently, so I'm not sure he'll see this. Maybe wait for a day or two just in case.

@jamesETsmith
Copy link
Contributor Author

@jamesETsmith I think it makes sense to create a subissues in #105184 so you can associate this PR with it.

@Zingam I don't have permission to add this as a subissue, but I agree that seems like a good idea. I don't have much experience contributing to libc++ so I'll defer to more senior people. @ldionne, would it be ok to add this as a subissue?

@@ -46,7 +46,7 @@
"`P2255R2 <https://wg21.link/P2255R2>`__","A type trait to detect reference binding to temporary","2022-02 (Virtual)","|Partial|","","Implemented the type traits only."
"`P2273R3 <https://wg21.link/P2273R3>`__","Making ``std::unique_ptr`` constexpr","2022-02 (Virtual)","|Complete|","16",""
"`P2387R3 <https://wg21.link/P2387R3>`__","Pipe support for user-defined range adaptors","2022-02 (Virtual)","|Complete|","19",""
"`P2440R1 <https://wg21.link/P2440R1>`__","``ranges::iota``, ``ranges::shift_left`` and ``ranges::shift_right``","2022-02 (Virtual)","","",""
"`P2440R1 <https://wg21.link/P2440R1>`__","``ranges::iota``, ``ranges::shift_left`` and ``ranges::shift_right``","2022-02 (Virtual)","|Partial| ``std::ranges::shift_left`` and ``std::ranges::shift_right`` have not been implemented","","|ranges|"
Copy link
Contributor

@Zingam Zingam Apr 2, 2025

Choose a reason for hiding this comment

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

This line doesn't seem right. The comment should go in the last column I think. Also maybe just claim what's implemented.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch, should be fixed now

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@Zingam lmk if it looks good to you now

@Zingam
Copy link
Contributor

Zingam commented Apr 2, 2025

@jamesETsmith I think it makes sense to create a subissues in #105184 so you can associate this PR with it.

@Zingam I don't have permission to add this as a subissue, but I agree that seems like a good idea. I don't have much experience contributing to libc++ so I'll defer to more senior people. @ldionne, would it be ok to add this as a subissue?

I created the sub-issues for you and associated the PR with it.

@jamesETsmith
Copy link
Contributor Author

jamesETsmith commented Apr 3, 2025

Just fixed the out of order imports that were causing the clang-fmt and clang-tidy failures on CI.

For reference, I couldn't reproduce the clang-tidy errors locally when running the libcxx/test/libcxx/clang_tidy.gen.py/algorithm.sh.cpp test that triggered one of the CI failures even though I had clearly added out_value_result.h out of order.

@philnik777 philnik777 merged commit 475cbf0 into llvm:main Apr 5, 2025
85 checks passed
@jamesETsmith jamesETsmith deleted the ranges_iota branch April 5, 2025 11:49
@Zingam
Copy link
Contributor

Zingam commented Apr 5, 2025

@jamesETsmith I think we should have an entry for this in the Release Notes:
https://github.com/llvm/llvm-project/blob/main/libcxx/docs/ReleaseNotes/21.rst

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. ranges Issues related to `<ranges>`
Projects
None yet
Development

Successfully merging this pull request may close these issues.

P2440R1: ranges::iota
9 participants