Basically the title.
Details
- Reviewers
cjdb ldionne EricWF • Quuxplusone Mordante - Group Reviewers
Restricted Project - Commits
- rG9106047ee3dd: [libcxx][ranges] Add range.subrange.
Diff Detail
- Repository
- rG LLVM Github Monorepo
Event Timeline
For now I only wanted to comment on the unsigned conversion. ATM I don't have time for a proper review.
libcxx/include/type_traits | ||
---|---|---|
2311–2314 | I would like _LIBCPP_INLINE_VISIBILITY and I dislike the _like suffix. Of course if we remove the _like it duplicates __to_unsigned defined in <charconv>. I start to feel it makes sense to make a header to do some of these signed to unsigned conversions. (I also use them for <format> but there I already require <charconv>. WDYT? (Note <charconv> is available in C++11 as an extension.) |
libcxx/include/type_traits | ||
---|---|---|
2311–2314 | This is already in main. I'm not sure why it's showing up in the diff. |
libcxx/include/type_traits | ||
---|---|---|
2311–2314 | I see thanks. Any objection to renaming it to __to_unsigned? Since <charconv> already depends on <type_traits> I'll make a patch to let <charconv> reuse this function. |
Please validate the visibility for all types
template classes -> _LIBCPP_TEMPLATE_VIS
enums -> _LIBCPP_ENUM_VIS
inline function -> _LIBCPP_INLINE_VISIBILITY
For now I only looked at the subrange header. Will look at the test at another time.
libcxx/include/__ranges/subrange.h | ||
---|---|---|
40 | Please __uglify t. | |
50 | The Standard uses not-same-as instead of !same_as. template<class T, class U> concept not-same-as = // exposition only !same_as<remove_cvref_t<T>, remove_cvref_t<U>>; Is there a reason to deviate? | |
63 | Just curious, why these names instead of _Tp, _Up, and _Vp? | |
70 | Why not using a bool as underlying type? The Standard uses enum class subrange_kind : bool { unsized, sized }; . | |
119 | Another case of !same_as vs not-same-as. When using __not_same_as this concept can be used instead of class like in the Standard. | |
159 | Please use NODISCARD_EXT. | |
161 | Can you rename this to __make_unsigned_t? | |
194 | Interesting, hopefully __n will never be std::numeric_limits<iter_difference_t<_Iter>>::min() | |
libcxx/include/type_traits | ||
2311–2314 | I've put up D102332. |
inline function -> _LIBCPP_INLINE_VISIBILITY
I thought this was only for internal functions, because public functions have stable pre/post conditions, is that not correct?
libcxx/include/__ranges/subrange.h | ||
---|---|---|
40 | Good idea about using __r. I should have documented this more clearly, but I'm going to "properly" add these concepts in a future patch. I just didn't realize they were a prerequisite until too late here, so I just copied them in. | |
63 | I think it's a bit clearer, then you don't have to map T to the caller and try to figure out which argument it is, you can just say, "oh that's supposed to be pair-like." | |
161 | I think this comment got moved. What do you mean? |
AFAIK it's done for all inline functions. (I wouldn't be surprised if there are more functions not having the marker.) If you, for example, look at the <bit> header all functions have this marker. Both the public and internal functions.
Of course it would be nice to have a CI job to catch the issues ;-)
libcxx/include/__ranges/concepts.h | ||
---|---|---|
72 ↗ | (On Diff #344990) | Why not in the namespace? |
libcxx/include/__ranges/subrange.h | ||
63 | Thanks for the information. | |
161 | I had another look and this time a make-unsigned-like-t doesn't mean exposition only type but make_unsigned_t depending on conditions. So it seems your code is correct. |
AFAIK it's done for all inline functions. (I wouldn't be surprised if there are more functions not having the marker.) If you, for example, look at the <bit> header all functions have this marker. Both the public and internal functions.
_LIBCPP_INLINE_VISIBILITY is synonymous for _LIBCPP_HIDE_FROM_ABI, right? (Side-note: are we trying to move to using the latter?) Anyway, I think it's just used to inline things so they're hidden from the ABI (i.e., the function goes away so there's nothing to get baked into the ABI). However, we don't want or need public functions to be hidden from the ABI. In fact, we probably don't want them to be hidden from the ABI, so that we can still have the benefits of ABI stability, and reduced code-size.
libcxx/include/__ranges/concepts.h | ||
---|---|---|
72 ↗ | (On Diff #344990) | I figured this is a more general utility. Not just for ranges. But this makes me think: it would probably be better to move this to <concepts>, actually. |
I'll review the tests later today, but I anticipate some of my feedback might shape the existing tests.
I'd also appreciate if we can split this test set into multiple files, similar to how test/std/containers/sequences/vector/ is broken down.
libcxx/include/__ranges/concepts.h | ||
---|---|---|
72 ↗ | (On Diff #344990) | Yes, please move to <concepts>. |
libcxx/include/__ranges/subrange.h | ||
49–52 | Checking that the comment isn't the opposite of what the code wants is a bit convoluted. I suggest making a named concept out of this bit to improve readability. | |
57 | I think the standard-provided comment is useful in improving readability. | |
116–120 | I believe you also need an overload for when __store_size == false. | |
135 | You'll probably need to guard on !__store_size && sized_range<R>, since it's possible for both to be true, thus creating an ambiguity. Please add a test for this very case. | |
142 | If we already have a make_unsigned_like_t, please use that. Otherwise, carry on. | |
144 | Standard uses braces for this one. | |
147 | Should be __not_same_as. | |
155 | Regardless of how the _LIBCPP_NODISCARD_EXT discussion plays out, this one should be [[nodiscard]]. | |
172 | Regardless of how the _LIBCPP_NODISCARD_EXT discussion plays out, this one should be [[nodiscard]]. | |
179 | Regardless of how the _LIBCPP_NODISCARD_EXT discussion plays out, this one should be [[nodiscard]]. | |
184 | Regardless of how the _LIBCPP_NODISCARD_EXT discussion plays out, this one should be [[nodiscard]]. |
libcxx/include/__ranges/subrange.h | ||
---|---|---|
144 |
For ranges wording, wherever braces have behavior different from parens, the use of braces is almost certainly a defect. P2367R0 is expected to fix all such occurrences. https://github.com/cplusplus/draft/issues/4593 deals with the rest. | |
147 | Who came up with the idea to have a exposition-only concept named not-same-as that's not not same_as? |
libcxx/include/__ranges/subrange.h | ||
---|---|---|
116–120 | Other than the one on L112? | |
135 | Hmm, I think you're right. So that would be the case where !sized_sentinel_for but it had a size member or something? | |
142 | I don't see one. | |
144 | Is there an observable difference/do we care? If there's no difference I try to use parens, because that shows there's no difference (if I use braces, a reader might ask "is there a reason we have to use braces here?"). |
libcxx/include/__ranges/subrange.h | ||
---|---|---|
147 | This is the most annoying thing... |
libcxx/include/__ranges/subrange.h | ||
---|---|---|
135 | For posterity: this is actually the opposite of what we need to guard on, we need to guard on __store_size && sized_range<R>. Because for this overload: constexpr subrange(R&& r) requires (!StoreSize || sized_range<R>); We do
However, it was just easier to write it as two overloads where we do
The problem is that didn't catch the case (as I said above) where both sized_range and !StoreSize are true, in which case it would be ambiguous. I fixed it and added DifferentSentienlWithSizeMember as a regression test. |
I think we're getting close. There still seem to be some open review comments and some comments marked as done, which aren't done. I added comment to them, be please verify whether I didn't overlook any. I still wonder about the visibility macros, but let's discuss that on Discord.
libcxx/include/__ranges/subrange.h | ||
---|---|---|
40 | Can you address this issue? | |
147 | I agree, I also dislike this idea. Can you use the __not_as_as? | |
184 | Agreed, these 3 require [[nodiscard]] according to the Standard. (No idea why this empty() doesn't need it.) | |
libcxx/test/std/ranges/range.utility/range.subrange/access.pass.cpp | ||
13 | Is this msvc work-around still required. IIRC it was due to some __ugly_macro_conflicts. | |
libcxx/test/std/ranges/range.utility/range.subrange/ctad.compile.pass.cpp | ||
27 | Please add some linebreaks to keep this readable. Same for the other places with these overly long lines. IIRC our current maximum column width is 120. | |
libcxx/test/std/ranges/range.utility/range.subrange/ctor.pass.cpp | ||
31 | Nice comment! |
libcxx/test/std/ranges/range.utility/range.subrange/ctor.pass.cpp | ||
---|---|---|
31 | Thanks :) |
LGTM, except for the [[nodiscard]] on empty(). Please make sure the build passes CI before committing.
libcxx/include/__ranges/subrange.h | ||
---|---|---|
160 | This one shouldn't be [[nodiscard]] according to the Standard. |
libcxx/include/__ranges/subrange.h | ||
---|---|---|
160 | Oke wasn't aware we decided that. But I agree calling size() and discarding the result is most likely a bug. |
General note for the tests - can we split tests so that we test one overload (as documented in the Standard) per file? This is what we do elsewhere and it makes it easier to make sure we're testing everything correctly. If a function is only a very tiny derivative of another one, then it's OK to test both in one shot, of course.
libcxx/include/__ranges/subrange.h | ||
---|---|---|
47 | This is *so* weird - I would have expected something like tuple_size<_Tp>::value == 2 instead. But leave as-is, that's how the Standard does it. | |
65 | For a static like that, I'd use _StoreSize instead. Re: using _Iter instead of _Tp, I strongly agree -- using _Iter is a lot clearer than what the Standard uses. | |
66 | __begin_? (same for other member variables) | |
75 | Here, we're going to store the size if _Kind == subrange_kind::sized even if _Sent is a sized sentinel for _Iter. We should not be storing it when _Sent is a sized sentinel. | |
98–100 | Instead, you could use this->member when referring to a member in the dependent base, and _Base::__store_size when referring to the static. IMO that's easier to follow and more idiomatic, WDYT? | |
143 | I'm surprised they didn't make this _Iter begin() && instead to make sure we can only call this on a xvalue. | |
149 | See, I'm very curious to understand why the Standard didn't mark this one as [[nodiscard]]. It can't be an oversight, since they marked a bunch of methods around it [[nodiscard]]. Just curious to understand what the motivation was. | |
libcxx/test/std/ranges/range.utility/range.subrange/access.pass.cpp | ||
89 | testPrimitives? | |
libcxx/test/std/ranges/range.utility/range.subrange/ctad.compile.pass.cpp | ||
22 | namespace ranges = std::ranges or something like that, but we frown upon importing names. |
libcxx/include/__ranges/subrange.h | ||
---|---|---|
65 | The style I try to use is that variables are always __snake_case or camelCase (regardless of staticness) and the former at least seems to be the existing style. This way, _TitleCase or TitleCase is reserved for types (also matches current style and is my personal preference). | |
66 | In recent commits, at least, I tend not to suffix with an underscore. Historically in libc++ we seem to do both. If you feel strongly, let's add some docs/style guid and I'll fix it. | |
81 | Nit: no constexpr. | |
98–100 | This is all personal preference, but the way I see it, there's no confusion as to what __begin is referring to and it's unconditionally a member. If someone went looking for the definition, they'd find this which would lead them to it. In fact, there might be an argument that saying this->member adds confusion, because someone might say "why do we need to use this->" or try to remove that. For me it really comes down to simplicity/brevity. Writing __begin is simply fewer characters. | |
143 | This is odd and does not seem to be inline with most views. |
libcxx/include/__ranges/subrange.h | ||
---|---|---|
50–51 | Check whether these should be ADL or std::get, and then (if the ADL is intentional) let's please figure out a way to document that in the code long-term. | |
66 | FWIW, I feel strongly: libc++ should follow libc++ style, which is "data members get underscore-suffixed." | |
98–100 | FWIW, I agree with @ldionne: using-declarations (and their effect on name lookup) are significantly more confusing for the reader/maintainer than the plain old this->__member_ expression syntax we're all familiar with. | |
143 | Tangential, but yeah: it wouldn't make sense to require the caller to write std::move(myRange).begin(), because then how would you ever get at the end-sentinel? You couldn't write again std::move(myRange).end(), because that would trigger all your static-analyzer's "double-moved-from" checks. | |
184 | this->__size_ += _VSTD::__to_unsigned_like(-__n); Orthogonally: Is the whole point of __store_size_ just to keep track of whether __size_ exists? Could we just do if constexpr (requires { this->__size_; }) this->__size_ += ...; instead, throughout? Or would that not-work for some technical reason? | |
209–210 | Formatting nit: the -> belongs on the second line. Lines 199-200 above do it right AFAIC. | |
libcxx/test/std/ranges/range.utility/range.subrange/access.pass.cpp | ||
59 | Definitely just write out std::ptrdiff_t in the like two places it appears. | |
74 | Formatting nit: friend constexpr | |
libcxx/test/std/ranges/range.utility/range.subrange/ctad.compile.pass.cpp | ||
22 | (moving my comment from the previous file after seeing Louis's) | |
27 | One way to clean this up is to introduce an array of int a and a typedef FI: int a[10]; using FI = forward_iterator<int*>; static_assert(std::same_as<decltype(subrange(FI(a), FI(a))), subrange<FI, FI, std::ranges::subrange_kind::unsized>>); static_assert(std::same_as<decltype(subrange(FI(a), FI(a), 0)), subrange<FI, FI, std::ranges::subrange_kind::sized>>); static_assert(std::same_as<decltype(subrange(a, a)), subrange<int*, int*, std::ranges::subrange_kind::unsized>>); static_assert(std::same_as<decltype(subrange(a, a, 0)), subrange<int*, int*, std::ranges::subrange_kind::sized>>); static_assert(std::same_as<decltype(subrange(a, nullptr)), subrange<int*, std::nullptr_t, std::ranges::subrange_kind::unsized>>); static_assert(std::same_as<decltype(subrange(a, nullptr, 0)), subrange<int*, std::nullptr_t, std::ranges::subrange_kind::sized>>); | |
libcxx/test/std/ranges/range.utility/range.subrange/ctor.pass.cpp | ||
49 | base_ | |
84 | value_ (but actually, I think you should just call this base_ — see? the underscore has a purpose! :)) | |
128–129 | I think there is not. (Also you wrote unsized for unsigned) | |
165 | s/enl/nel/g | |
libcxx/test/std/ranges/range.utility/range.subrange/general.compile.pass.cpp | ||
24 | Formatting nit: class... Args with a space. |
libcxx/include/__ranges/subrange.h | ||
---|---|---|
65 | Ack. | |
66 | @zoecarver If you haven't been using underscores lately, then it's simply an oversight in the code review. libc++ has been using trailing underscores for members forever, this isn't something new. We could definitely benefit from documenting the coding style we want, however fixing the issue in this review shouldn't be gated on that. | |
98–100 | Arthur's comment is specifically why I requested that change. |
libcxx/include/__ranges/subrange.h | ||
---|---|---|
49–52 | I think it's important to make sure this is the same as the standard naming. Luckily the standard changed this for us. | |
50–51 | I've been informed that these are not ADL. Fixed. | |
66 | OK I'll update these. Also, I'll add to my list of things I probably won't ever find time for: configure the clang tidy rule for this. | |
116–120 | @cjdb Ping. | |
149 | I'm just guessing here, but maybe LWG didn't think that empty was sufficiently vexing. As I've already discussed, it makes sense to mark next/prev nodiscard because there are multiple APIs that do different things there, and begin needs to be nodiscard because it's moving the iterator. Empty doesn't have anything "special" (and reason to mark it nodiscard) other than a confusing name. | |
184 | Any reason for this-> over _Base::?
Pretty sure there's a clang bug that doesn't allow us to use inline-requires like this. | |
libcxx/test/std/ranges/range.utility/range.subrange/ctad.compile.pass.cpp | ||
27 | Did something similar, it's a bit better now. | |
libcxx/test/std/ranges/range.utility/range.subrange/ctor.pass.cpp | ||
128–129 | Removed. Thanks. |
libcxx/test/std/ranges/range.utility/range.subrange/ctor.pass.cpp | ||
---|---|---|
165 | I'm bad at spotting these, so if you see others let me know and I'll fix them. |
(I'd like @cjdb to take a final look at this and give the green light if he's fine with it, since he had a previous requires-changes)
libcxx/include/__ranges/subrange.h | ||
---|---|---|
184 | I strongly prefer this->__size_. Don't use weird syntax unless there's a technical reason for it. |
Thanks for working hard on this!
libcxx/include/__ranges/subrange.h | ||
---|---|---|
98–100 | I'm with Zoe on this one. We have an underscore-suffix-for-members convention, so it should be clear that __member_ is shorthand for this->__member_, and _Base::__member_ is for the base's member. Whether or not it's an instance member or a static member feels largely irrelevant to me? | |
116–120 | Ah, yes, I missed that, sorry. | |
144 | Yes. Tim's GH link has all the relevant info. |
The above update:
- Split tests into multiple files.
- Rebased onto D102468 and applied changes from Barry's paper.
Please __uglify t.
Minor nit, since you use _Range instead of _Tp, how do you feel about using __r instead of __t?