Details
- Reviewers
ldionne EricWF Mordante miscco curdeius zoecarver • Quuxplusone - Group Reviewers
Restricted Project - Commits
- rGfe31f11cc821: [libcxx] adds `std::incrementable_traits` to <iterator>
Diff Detail
- Repository
- rG LLVM Github Monorepo
Event Timeline
libcxx/test/std/iterators/iterator.requirements/iterator.assoc.types/incrementable.traits/incrementable_traits.pass.cpp | ||
---|---|---|
44 | This is surprising, but I guess it falls out naturally because int[] is subtractable? |
libcxx/include/iterator | ||
---|---|---|
459 | This is the only reason <iterator> needs to include <concepts> in C++20 mode, right? template<class _Tp> concept __has_integral_minus = !__has_difference_type_member<_Tp> && requires(const _Tp& __x, const _Tp& __y) { requires is_integral_v<decltype( __x - __y )>; }; (And/or, wait for <concepts> to stop depending on <iterator>; D99041, D99124.) I note that the name __has_integral_minus is slightly misleading; it currently means __has_integral_minus_without_difference_type. The paper standard doesn't factor out these concepts, so we have no exposition-only names to imitate here. Maybe __incrementable_traits_use_difference_type<_Tp> and __incrementable_traits_use_minus<_Tp> would be better? But I don't really object to the current name even if I do find it "slightly misleading." | |
469 | Just declval<_Tp>(), not std::declval<_Tp>(). (If it had any arguments, then definitely _VSTD::declval to prevent ADL — but it has no arguments. Actually we still have a ton of places that do _VSTD::declval, so I'm okay with that spelling too. Just not std::.) | |
libcxx/test/std/iterators/iterator.requirements/iterator.assoc.types/incrementable.traits/incrementable_traits.pass.cpp | ||
23 | Almost all places where you write incrementable_traits, you should be writing difference_type — has_difference_type, difference_type_matches, check_difference_type. | |
159 | "with_cv"? I see no cv-qualifiers here. |
applies @Quuxplusone's feedback
libcxx/include/iterator | ||
---|---|---|
459 |
For this patch? Yes. It's a part of the synopsis, so in general, no.
That was my intention, hence "Depends on D99041".
Right. I think I got the idea of naming these from readable.traits, so __has_member_difference_type (sic) is probably best for consistency. As for __has_integral_minus, I'm ambivalent, so long as it's consistent. | |
469 | Eh, ADL isn't a concern here. Removed. | |
libcxx/test/std/iterators/iterator.requirements/iterator.assoc.types/incrementable.traits/incrementable_traits.pass.cpp | ||
44 | See below for int(*)() and friends. |
Can you also make sure the patch passes CI?
libcxx/include/iterator | ||
---|---|---|
16 | Can you add the required includes? #include <compare> // see [compare.syn] #include <concepts> // see [concepts.syn] | |
429 | Maybe also add compare while you're at it. | |
459 | The synopsis requires concepts http://eel.is/c++draft/iterator.synopsis | |
libcxx/test/std/iterators/iterator.requirements/iterator.assoc.types/incrementable.traits/incrementable_traits.pass.cpp | ||
2 | http://eel.is/c++draft/incrementable.traits#3 Users may specialize incrementable_traits on program-defined types. |
libcxx/test/std/iterators/iterator.requirements/iterator.assoc.types/incrementable.traits/incrementable_traits.pass.cpp | ||
---|---|---|
32–37 | Take the suggested edit. | |
44 | Please add int[10]. | |
223 | My usual complaints about hundreds of lines of tests, which test very little.
|
libcxx/test/std/iterators/iterator.requirements/iterator.assoc.types/incrementable.traits/incrementable_traits.pass.cpp | ||
---|---|---|
32–37 | The suggested edit doesn't add anything constructive and appears to be a stylistic preference, so there's nothing to change. | |
223 |
As mentioned in previous patches: I have been asked to add tests, and my comprehensive coverage has been encouraged by several others, including two maintainers. There are many lines, yes, but each of these tests ensures that cv-qualifiers and reference qualifiers are correctly handled.
I have added these three and more. |
Thanks!
libcxx/include/iterator | ||
---|---|---|
472 | Hmm. I really feel like we should have a better way to track what is implemented, what is blocked on other patches, etc. TODOs might be OK for now, but I'd like to sort this out. | |
libcxx/test/std/iterators/iterator.requirements/iterator.assoc.types/incrementable.traits/incrementable_traits.pass.cpp | ||
223 | I don't feel opposition to lots of tests, even if they might seem excessive. Though, it might be good to have a few simple tests at the top or bottom that are easily verifiable and human-readable that just show "this works right in the common case". That being said, this patch LGTM (but let's make sure Arthur is also OK with it). |
libcxx/test/std/iterators/iterator.requirements/iterator.assoc.types/incrementable.traits/incrementable_traits.pass.cpp | ||
---|---|---|
223 | Well, I wouldn't say I'm "OK" with it; but I agree I haven't got the clout to block this patch series until it looks the way I think it should. The number of tests in a patch should generally be proportional to the number of lines-of-code changed: here Chris is adding 24 LOC, so the tests should be on that order of magnitude. Another way to put it is, test-code is just like ordinary code. Writing hundreds of lines of code can be useful, but it can also indicate that you need to take a step back to look at the problem you're trying to solve. Once you understand the problem being solved, you might find that an elegant fix is available. The hundred-line rough draft can still be useful! Looking at Chris's "rough draft" with its hundreds of lines of const_lvalue_ref_volatile_rvalue_ref_subtraction gave me the clue "okay, cvref-qualifiers are important to this code somehow." Then I looked at the problem being solved — "okay, we need to test that something might bit-rot in this code, in the area of cvref-qualifiers specifically." That's how I found that none of these tests were testing what they intended. The next step is to write a surgical patch that solves the problem; I think I can write the new test for each of the three bullet points above in about 5 lines each. With the smaller patch in hand, the big patch is no longer technically necessary — and therefore I personally wouldn't check it in. (Although, again, I understand that I'm not the gatekeeper here, and the big patch is quite likely to get checked in regardless.) |
Can you fix the Apple build failures? (Guess it's caused by the parent commit.)
libcxx/test/std/iterators/iterator.requirements/iterator.assoc.types/incrementable.traits/incrementable_traits.pass.cpp | ||
---|---|---|
223 | I'm personally also not too fond of the amount of tests and I agree with @Quuxplusone. However some maintainers have asked for verbose tests for some of the concepts in earlier reviews. Maybe we should discuss on discord what we expect of unit tests and how verbose. I don't like to block this review on that discussion; the tests aren't wrong and we can always reduce the number of tests later. |
libcxx/include/__config | ||
---|---|---|
851 ↗ | (On Diff #335200) | Please use <= 17, for consistency with similar lines in this file. (We tend to guard C++2a stuff on > 17. I don't think there's anything special about the Ranges stuff in this respect, right?) |
libcxx/test/std/iterators/iterator.requirements/iterator.assoc.types/incrementable.traits/incrementable_traits.pass.cpp | ||
35–36 | Note that T const is the same type as T whenever T is a reference type. Therefore, any place in this file that instantiates check_incrementable_traits<Foo&> is suspicious — it's simply static-asserting that the difference type of T is the difference type of T. assert(result); inside this function. (Or is it okay if result is false, sometimes? I didn't closely examine all the call sites.) |
I'd like to put an end to comments about "too many tests" once and for all. I think we all agree that test coverage is a good property to have. I think we also agree that readability is a good property to have, and that code duplication is bad. We want both, as much as possible.
I think what we're seeing here is an example where we have a lot of code coverage, which is great, however the cost (right now) is a lot of repetition, and hence decreased readability. For me, the solution is not to remove the test coverage, but instead to implement it without reducing readability so much. Here's an example of how this can be done (there are other ways, for example we could use templates instead but I just hacked this together). This replaces lines 174 to 702. Yes it's still not *small*, but it's much better and IMO easier to read, while not dropping any of the coverage. And it would be easy to make it even better by using two nested "loops" over all qualifiers, but it's unclear to me that would actually increase readability.
#define TEST_NOT_DIFFERENCE_TYPE(qual1, qual2) \ struct TEST_CONCAT(test_subtraction_,__LINE__) { \ friend int operator-(TEST_CONCAT(test_subtraction_,__LINE__) qual1, \ TEST_CONCAT(test_subtraction_,__LINE__) qual2); \ }; \ static_assert(!check_has_difference_type<TEST_CONCAT(test_subtraction_,__LINE__)>) \ /**/ TEST_NOT_DIFFERENCE_TYPE(&&, &&); TEST_NOT_DIFFERENCE_TYPE(&, &); TEST_NOT_DIFFERENCE_TYPE(&, const&); TEST_NOT_DIFFERENCE_TYPE(&, volatile&); TEST_NOT_DIFFERENCE_TYPE(&, const volatile&); TEST_NOT_DIFFERENCE_TYPE(&, &&); TEST_NOT_DIFFERENCE_TYPE(&, const&&); TEST_NOT_DIFFERENCE_TYPE(&, volatile&&); TEST_NOT_DIFFERENCE_TYPE(&, const volatile&&); TEST_NOT_DIFFERENCE_TYPE(const&, &); TEST_NOT_DIFFERENCE_TYPE(const&, volatile&); TEST_NOT_DIFFERENCE_TYPE(const&, &&); TEST_NOT_DIFFERENCE_TYPE(const&, const&&); TEST_NOT_DIFFERENCE_TYPE(const&, volatile&&); TEST_NOT_DIFFERENCE_TYPE(const&, const volatile&&); TEST_NOT_DIFFERENCE_TYPE(volatile&, &); TEST_NOT_DIFFERENCE_TYPE(volatile&, const&); TEST_NOT_DIFFERENCE_TYPE(volatile&, volatile&); TEST_NOT_DIFFERENCE_TYPE(volatile&, const volatile&); TEST_NOT_DIFFERENCE_TYPE(volatile&, &&); TEST_NOT_DIFFERENCE_TYPE(volatile&, const&&); TEST_NOT_DIFFERENCE_TYPE(volatile&, volatile&&); TEST_NOT_DIFFERENCE_TYPE(volatile&, const volatile&&); TEST_NOT_DIFFERENCE_TYPE(const volatile&, &); TEST_NOT_DIFFERENCE_TYPE(const volatile&, volatile&); TEST_NOT_DIFFERENCE_TYPE(const volatile&, &&); TEST_NOT_DIFFERENCE_TYPE(const volatile&, const&&); TEST_NOT_DIFFERENCE_TYPE(const volatile&, volatile&&); TEST_NOT_DIFFERENCE_TYPE(const volatile&, const volatile&&); TEST_NOT_DIFFERENCE_TYPE(&&, &); TEST_NOT_DIFFERENCE_TYPE(&&, const&); TEST_NOT_DIFFERENCE_TYPE(&&, volatile&); TEST_NOT_DIFFERENCE_TYPE(&&, const volatile&); TEST_NOT_DIFFERENCE_TYPE(&&, &&); TEST_NOT_DIFFERENCE_TYPE(&&, const&&); TEST_NOT_DIFFERENCE_TYPE(&&, volatile&&); TEST_NOT_DIFFERENCE_TYPE(&&, const volatile&&); TEST_NOT_DIFFERENCE_TYPE(const&&, &); TEST_NOT_DIFFERENCE_TYPE(const&&, const&); TEST_NOT_DIFFERENCE_TYPE(const&&, volatile&); TEST_NOT_DIFFERENCE_TYPE(const&&, const volatile&); TEST_NOT_DIFFERENCE_TYPE(const&&, &&); TEST_NOT_DIFFERENCE_TYPE(const&&, const&&); TEST_NOT_DIFFERENCE_TYPE(const&&, volatile&&); TEST_NOT_DIFFERENCE_TYPE(const&&, const volatile&&); TEST_NOT_DIFFERENCE_TYPE(volatile&&, &); TEST_NOT_DIFFERENCE_TYPE(volatile&&, const&); TEST_NOT_DIFFERENCE_TYPE(volatile&&, volatile&); TEST_NOT_DIFFERENCE_TYPE(volatile&&, const volatile&); TEST_NOT_DIFFERENCE_TYPE(volatile&&, &&); TEST_NOT_DIFFERENCE_TYPE(volatile&&, const&&); TEST_NOT_DIFFERENCE_TYPE(volatile&&, volatile&&); TEST_NOT_DIFFERENCE_TYPE(volatile&&, const volatile&&); TEST_NOT_DIFFERENCE_TYPE(const volatile&&, &); TEST_NOT_DIFFERENCE_TYPE(const volatile&&, const&); TEST_NOT_DIFFERENCE_TYPE(const volatile&&, volatile&); TEST_NOT_DIFFERENCE_TYPE(const volatile&&, const volatile&); TEST_NOT_DIFFERENCE_TYPE(const volatile&&, &&); TEST_NOT_DIFFERENCE_TYPE(const volatile&&, const&&); TEST_NOT_DIFFERENCE_TYPE(const volatile&&, volatile&&); TEST_NOT_DIFFERENCE_TYPE(const volatile&&, const volatile&&); TEST_NOT_DIFFERENCE_TYPE(&, /*by value*/); TEST_NOT_DIFFERENCE_TYPE(const&, /*by value*/); TEST_NOT_DIFFERENCE_TYPE(volatile&, /*by value*/); TEST_NOT_DIFFERENCE_TYPE(&&, /*by value*/); TEST_NOT_DIFFERENCE_TYPE(const&&, /*by value*/); TEST_NOT_DIFFERENCE_TYPE(volatile&&, /*by value*/); TEST_NOT_DIFFERENCE_TYPE(const volatile&&, /*by value*/); TEST_NOT_DIFFERENCE_TYPE(/*by value*/, &); TEST_NOT_DIFFERENCE_TYPE(/*by value*/, const&); TEST_NOT_DIFFERENCE_TYPE(/*by value*/, volatile&); TEST_NOT_DIFFERENCE_TYPE(/*by value*/, &&); TEST_NOT_DIFFERENCE_TYPE(/*by value*/, const&&); TEST_NOT_DIFFERENCE_TYPE(/*by value*/, volatile&&); TEST_NOT_DIFFERENCE_TYPE(/*by value*/, const volatile&&);
WDYT? If we have to do this sort of thing frequently, we could also bundle a few utilities under support/ to reduce duplication further.
I'd like to put an end to comments about "too many tests" once and for all. I think we all agree that test coverage is a good property to have. I think we also agree that readability is a good property to have, and that code duplication is bad. We want both, as much as possible.
[...]
I really like your suggestion. The only two things I'd say are
- We could take that even further and have a test macro that generates all the const/ref/volatile combinations we want (this is sort of like what you were saying with the two loops). Then we could just pass it a macro that generated the tests for any given concept.
- These tests are good because they cover as much code as possible, but they make it harder to verify what's actually being tested (especially if it's two macros deep). I'd suggest that we have the macro tests and some simple, easily verifyable tests that verify the basic/common case.
Using macros like this will also make it easy to write lots of tests quickly, so it shouldn't be too hard to back port to the other concepts that have already landed.
Now that I know we're not afraid of macros, I'll employ this where large tests are necessary.
libcxx/include/__config | ||
---|---|---|
851 ↗ | (On Diff #335200) |
Done.
Not sure I'm following this line of thought, so I'm leaving the comment as unresolved for now. |
libcxx/include/iterator | ||
472 | In general: yes please. | |
libcxx/test/std/iterators/iterator.requirements/iterator.assoc.types/incrementable.traits/incrementable_traits.pass.cpp | ||
35–36 |
Lines 63-70 should already account for this, unless you're asking we check T& against T const& and friends.
Lines 89 and 90-1 expect false. |
I really like @ldionne suggestion. I am a bit unhappy about it being a macro, but meh.
FYI, this is the MSVC test set which is kind of the same but less extensive:
This still LGTM. I think we should land this to get stuff unblocked! (After another reviewer approves.)
libcxx/include/__config | ||
---|---|---|
851 ↗ | (On Diff #335200) |
I don't think this is needed anymore, because C++20 has been released. We did this pre-c++20 (when it was still C++2a) because we weren't sure it would be released in C++20 (i.e., if there was a global pandemic...). But, it was still released on time, and it is C++20, so I think we can guard on that now. |
libcxx/test/std/iterators/iterator.requirements/iterator.assoc.types/incrementable.traits/incrementable_traits.compile.pass.cpp | ||
149 ↗ | (On Diff #335404) | What's the comment for? |
245 ↗ | (On Diff #335404) | What's going on here? Why is this commented? |
libcxx/include/__config | ||
---|---|---|
851 ↗ | (On Diff #335200) | I still don't understand, sorry 😭 |
libcxx/test/std/iterators/iterator.requirements/iterator.assoc.types/incrementable.traits/incrementable_traits.compile.pass.cpp | ||
149 ↗ | (On Diff #335404) | Paging @ldionne. He's not the only Boost dev I've seen do this. |
245 ↗ | (On Diff #335404) | I think it would be best to keep the commented out lines there to show these permutations weren't forgotten, but they can't be enabled because they actually pass. |
libcxx/test/std/iterators/iterator.requirements/iterator.assoc.types/incrementable.traits/incrementable_traits.compile.pass.cpp | ||
---|---|---|
149 ↗ | (On Diff #335404) | Isn't it because line 148 ends with a \? Just to avoid when writing code on line 149 to be part of the macro. |
libcxx/test/std/iterators/iterator.requirements/iterator.assoc.types/incrementable.traits/incrementable_traits.compile.pass.cpp | ||
---|---|---|
149 ↗ | (On Diff #335404) | Yeah, but why does line 148 end with \? |
Ship it with comments applied and CI passing!
libcxx/include/__config | ||
---|---|---|
852 ↗ | (On Diff #335404) | FWIW, I'm fine with defining this for now, however as soon as we only support compilers that implement concepts, we will get rid of this and instead use just #if _LIBCPP_STD_VER >= 20 to guard concepts code. Agreed? |
libcxx/include/iterator | ||
461 | IMO this name is misleading because you're really checking whether T has an integral minus AND no member difference type. I think it would be cleaner to take the !__has_member_difference_type out of this helper concept and use it directly below in incrementable_traits. | |
libcxx/test/std/iterators/iterator.requirements/iterator.assoc.types/incrementable.traits/incrementable_traits.compile.pass.cpp | ||
149 ↗ | (On Diff #335404) | Good question, I do it as a reflex now. I'm not sure where it came from originally. I know at some point I had a tool to automatically add \ at the end of macro lines and it would be keyed on /**/ to stop IIRC, but I'm not sure why we do this historically. |
153 ↗ | (On Diff #335404) | You could use this common idiom to show that you explicitly wanted the macro to expand to nothing, and you didn't forget to escape the line break with \: #define NO_QUALIFIER /*nothing*/ |
libcxx/include/iterator | ||
---|---|---|
444 | This breaks using this header with -std=c++14 apparently: http://45.33.8.238/linux/43983/step_4.txt Looks like _LIBCPP_HAS_NO_RANGES is supposed to be defined in that case, but maybe it doesn't work right? |
libcxx/include/iterator | ||
---|---|---|
444 | Oh wait, this might be a bot config problem. Please ignore for now. |
libcxx/include/iterator | ||
---|---|---|
444 | Yes, was a problem on my end. All good now, sorry for the noise. |
Can you add the required includes?
http://eel.is/c++draft/iterator.synopsis