[libcxx-commits] [PATCH] D120180: [libc++][ranges] Implement changes to reverse_iterator from One Ranges Proposal.

Konstantin Varlamov via Phabricator via libcxx-commits libcxx-commits at lists.llvm.org
Thu Feb 24 23:12:31 PST 2022


var-const added inline comments.


================
Comment at: libcxx/include/__iterator/iterator_traits.h:145
+// The `cpp17-*-iterator` exposition-only concepts are easily confused with the Cpp17*Iterator tables, so the
+// exposition-only concepts have been banished to a namespace that makes it obvious they have a niche use-case.
 namespace __iterator_traits_detail {
----------------
Quuxplusone wrote:
> var-const wrote:
> > This is a drive-by change: I just found the original comment unclear.
> Sure. FWIW, I'd just remove this comment. It kinda seems like the only point of this comment is to explain something that the comment itself says is "obvious," which means it's like //self-referentially// bad. Plus, I don't even know what it's talking about. If "the Cpp17*Iterator tables" make an appearance in libc++'s codebase, I don't know where.
> 
> Perhaps all this comment means is: "`__iterator_traits_detail::__cpp17_iterator` is not the same as `__is_cpp17_input_iterator`." But that really //is// obvious, and only serves to raise more questions, like "Wait, why isn't it?" So again, I think the solution is to delete the confusing comment.
I agree that the comment is still vague and confusing; it presumes the reader has a lot of context and leaves that context out. I think the most confusing part is "Cpp17*Iterator tables" (which is also the part you pointed out). Most likely, it refers to [`iterator.cpp17`](https://eel.is/c++draft/iterator.cpp17), which indeed contains some tables (though mentioning them here is at best unhelpful). I tweaked the wording a bit -- do you think it reads better now?


================
Comment at: libcxx/include/__iterator/reverse_iterator.h:49
+#if !defined(_LIBCPP_HAS_NO_CONCEPTS)
+    static_assert(__is_cpp17_bidirectional_iterator<_Iter>::value || bidirectional_iterator<_Iter>, "The Iterator "
+        "template argument must be a bidirectional iterator.");
----------------
Quuxplusone wrote:
> var-const wrote:
> > http://eel.is/c++draft/reverse.iterators#reverse.iter.requirements-1:
> > 
> > > "The template parameter `Iterator` shall either meet the requirements of a `Cpp17BidirectionalIterator` (`[bidirectional.iterators]`) or model `bidirectional_­iterator` (`[iterator.concept.bidir]`)."
> > 
> > My understanding is that this is IFNDR, but adding a check seems like it could prevent harder-to-read errors during instantiation.
> > 
> > There's a similar requirement for random access iterators:
> > 
> > > "Additionally, `Iterator` shall either meet the requirements of a `Cpp17RandomAccessIterator` (`[random.access.iterators]`) or model `random_­access_­iterator` (`[iterator.concept.random.access]`) if the definitions of any of the members `operator+`, `operator-`, `operator+=`, `operator-=` (`[reverse.iter.nav]`), or `operator[]` (`[reverse.iter.elem]`), or the non-member operators (`[reverse.iter.cmp]`) `operator<`, `operator>`, `operator<=`, `operator>=`, `operator-`, or `operator+` (`[reverse.iter.nonmember]`) are instantiated (`[temp.inst]`)."
> > 
> > I'm not sure that can be done with a `static_assert` -- I tried placing it into the bodies of the mentioned operators, and the compiler produces errors trying to instantiate non-existing functions before it evaluates the `static_assert`.
> clang-format strikes again, in the formatting here; please fix it up somehow.
> I'm ambivalent on adding this `static_assert`. It will probably //worsen// the error messages, actually, since "such-and-such operator doesn't exist when I tried to use it" is generally much easier to deal with than "this random static_assert was false instead of true." Have you actually looked at the error messages for e.g. `reverse_iterator<std::forward_list<int>::iterator>`, and //do// you think this is an improvement?
> Here's the state of the art, for comparison: https://godbolt.org/z/nne1Mvzan
> ```
> __iterator/reverse_iterator.h:120:37: error: cannot decrement value of type 'std::__forward_list_iterator<std::__forward_list_node<int, void *> *>'
>     reverse_iterator& operator++() {--current; return *this;}
>                                     ^ ~~~~~~~
> ```
> I predict you'll find that this static_assert is not an improvement, and decide not to keep it.
I'm actually formatting most of these manually (don't want to get used to the convenience of clang-format when there's a chance it's taken away :) ). Reformatted this bit with clang-format now, I think it's an improvement.

Here's the full error I get with a static assert upon trying to create a reverse iterator from a forward iterator:

```
In file included from /Users/varconst/work/llvm-project/libcxx/test/std/iterators/predef.iterators/reverse.iterators/reverse.iter.cons/ctor.default.pass.cpp:15:
In file included from /Users/varconst/work/llvm-project/build/include/c++/v1/iterator:669:
In file included from /Users/varconst/work/llvm-project/build/include/c++/v1/__iterator/reverse_access.h:14:
/Users/varconst/work/llvm-project/build/include/c++/v1/__iterator/reverse_iterator.h:49:5: error: static_assert failed due to requirement '__is_cpp17_bidirectional_iterator<forward_iterator<int *>>::value || bidirectional_iterator<forward_iterator<int *>>' "The Iterator template argument must be a bidirectional iterator."
    static_assert(__is_cpp17_bidirectional_iterator<_Iter>::value || bidirectional_iterator<_Iter>,
    ^             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/varconst/work/llvm-project/libcxx/test/std/iterators/predef.iterators/reverse.iterators/reverse.iter.cons/ctor.default.pass.cpp:40:13: note: in instantiation of template class 'std::reverse_iterator<forward_iterator<int *>>' requested here
    BadIter i;
            ^
1 error generated.
```

Compared to the baseline (where construction succeeds but e.g. trying to increment the iterator fails):
```
In file included from /Users/varconst/work/llvm-project/libcxx/test/std/iterators/predef.iterators/reverse.iterators/reverse.iter.cons/ctor.default.pass.cpp:15:
In file included from /Users/varconst/work/llvm-project/build/include/c++/v1/iterator:669:
In file included from /Users/varconst/work/llvm-project/build/include/c++/v1/__iterator/reverse_access.h:14:
/Users/varconst/work/llvm-project/build/include/c++/v1/__iterator/reverse_iterator.h:148:37: error: cannot decrement value of type 'forward_iterator<int *>'
    reverse_iterator& operator++() {--current; return *this;}
                                    ^ ~~~~~~~
/Users/varconst/work/llvm-project/libcxx/test/std/iterators/predef.iterators/reverse.iterators/reverse.iter.cons/ctor.default.pass.cpp:42:5: note: in instantiation of member function 'std::reverse_iterator<forward_iterator<int *>>::operator++' requested here
    ++i;
    ^
1 error generated.
```

For easier comparison, the same messages with all the cruft removed:
```
__iterator/reverse_iterator.h:49:5: error: static_assert failed due to requirement '__is_cpp17_bidirectional_iterator<forward_iterator<int *>>::value || bidirectional_iterator<forward_iterator<int *>>' "The Iterator template argument must be a bidirectional iterator."
    static_assert(__is_cpp17_bidirectional_iterator<_Iter>::value || bidirectional_iterator<_Iter>,
    ^             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
```

```
__iterator/reverse_iterator.h:148:37: error: cannot decrement value of type 'forward_iterator<int *>'
    reverse_iterator& operator++() {--current; return *this;}
                                    ^ ~~~~~~~
```

I do think that the static assertion improves the error message slightly. The error is caught early and the message explains the fundamental problem rather than a symptom of the problem. Error messages like the current one are often cited as one of the problems with C++ templates -- showing a technical detail in the instantiation. While it's not hard to figure out why a reverse iterator needs the decrement operator, it's still an additional mental step, and I think saying that the iterator has to be bidirectional is a better way to express the requirement than dealing with individual requirements of a bidirectional iterator.


================
Comment at: libcxx/include/__iterator/reverse_iterator.h:81
+    using reference         = typename iterator_traits<_Iter>::reference;
 #endif
 
----------------
Quuxplusone wrote:
> Please group these blocks together; see what I did in https://reviews.llvm.org/D117656#change-18S54d7VLfJo
> I also recommend getting rid of the cute space-alignment formatting as long as you're in the area.
PTAL -- is this what you had in mind?


================
Comment at: libcxx/include/__iterator/reverse_iterator.h:139-146
+    pointer  operator->() const
+#if !defined(_LIBCPP_HAS_NO_CONCEPTS)
+    requires is_pointer_v<_Iter>
+      || requires(const _Iter i) { i.operator->(); }
+#endif
+    {
+      return _VSTD::addressof(operator*());
----------------
Quuxplusone wrote:
> I think what I'd do here is
> ```
> #if _LIBCPP_STD_VER > 17
>     pointer operator->() const
> #if !defined(_LIBCPP_HAS_NO_CONCEPTS)
>       requires is_pointer_v<_Iter> || requires(const _Iter i) { i.operator->(); }
> #endif // !defined(_LIBCPP_HAS_NO_CONCEPTS)
>     {
>       _Iter __tmp = current;
>       --__tmp;
>       if constexpr (is_pointer_v<_Iter>) {
>         return __tmp;
>       } else {
>         return _VSTD::move(__tmp).operator->();
>       }
>     }
> #else
>     pointer operator->() const {
>       return _VSTD::addressof(operator*());
>     }
> #endif // _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_CONCEPTS)
> ```
> Drive-by removed the cute spacing in `pointer  operator->`.
Done (with very slight modification).

Do you think it's worth thinking whether it's possible to test for the change in the effects of this function in C++20?


================
Comment at: libcxx/include/__iterator/reverse_iterator.h:157
     _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX14
-    reverse_iterator  operator+ (difference_type __n) const {return reverse_iterator(current - __n);}
+    reverse_iterator  operator+ (difference_type __n) const { return reverse_iterator(current - __n); }
+
----------------
Quuxplusone wrote:
> Either don't mess with the spacing at all, or fully update it as shown. (I have a slight preference for "don't mess with," but either is OK.)
Updated.


================
Comment at: libcxx/include/__iterator/reverse_iterator.h:172-178
+    _LIBCPP_HIDE_FROM_ABI friend constexpr
+    iter_rvalue_reference_t<_Iter> iter_move(const reverse_iterator& __i)
+    noexcept(is_nothrow_copy_constructible_v<_Iter>
+        && noexcept(ranges::iter_move(--declval<_Iter&>()))) {
+      auto __tmp = __i.base();
+      return ranges::iter_move(--__tmp);
+    }
----------------
Quuxplusone wrote:
> clang-format strikes again. Ditto `iter_swap`.
Nope, I simply prefer this formatting. Changed.


================
Comment at: libcxx/include/iterator:223
+    using iterator_category = see below; // since C++20
+    using value_type        = typename iterator_traits<Iterator>::value_typTESTED e; // since C++17, until C++20
+    using value_type        = iter_value_t<Iterator>; // since C++20
----------------
Quuxplusone wrote:
> 
Thanks!


================
Comment at: libcxx/test/libcxx/iterators/predef.iterators/reverse.iterators/bad_template_argument.fail.cpp:22-23
+  {
+    using BadIter = std::reverse_iterator<forward_iterator<int*>>;
+    BadIter i; //expected-error-re@*:* {{static_assert failed{{.*}} "The Iterator template argument must be a bidirectional iterator."}}
+  }
----------------
Quuxplusone wrote:
> I believe you wanted this to be `.verify.cpp`, not `.fail.cpp`. I think (but am not sure) that `.fail.cpp` just checks for //any// compiler failure, and doesn't actually respect `//expected-error` comments.
> 
> However, I expect this to be moot when you decide to remove the static_assert again. :)
I just checked and it behaves exactly the same (correctly) with either name. So probably the implementations of `verify` and `fail` converged at some point?


================
Comment at: libcxx/test/std/iterators/predef.iterators/reverse.iterators/iterator_concept_conformance.compile.pass.cpp:58-60
+using sized_iterator = random_access_iterator<int*>;
+static_assert( std::sized_sentinel_for<sized_iterator, sized_iterator>);
+static_assert( std::sized_sentinel_for<std::reverse_iterator<sized_iterator>, std::reverse_iterator<sized_iterator>>);
----------------
Quuxplusone wrote:
> These three lines don't seem to add anything, but it would be nice to have a test for some type `B` such that `bidirectional_iterator<B> && !random_access_iterator<B> && sized_sentinel_for<B, B>`. Unfortunately I think we don't have such a type in test_iterators.h, but it might take only ~15 lines to write one here (especially since you don't have to //implement// any of its methods, just declare them).
> 
> I also think you should actually split these lines out into `reverse.iterators/sized_sentinel.compile.pass.cpp`, and add another test for a type `R` such that `essentially-random_access_iterator<R> && disable_sized_sentinel_for<R, R>`, and make sure that `requires { reverse_iterator<R>() - reverse_iterator<R>(); } == false`.
> These three lines don't seem to add anything

The main intention for these lines is to provide more context for the actual test rather than test anything on their own. Basically, "if the underlying iterators are sized, sized sentinel for `reverse_iterator` works; otherwise, it doesn't". They also verify that the reason `sized_sentinel_for` is false is because of the underlying iterators, not because it's written in such a way that it's always false.


================
Comment at: libcxx/test/std/iterators/predef.iterators/reverse.iterators/iterator_concept_conformance.compile.pass.cpp:22
   static_assert(std::sentinel_for<I1, I1>);
-  static_assert(std::sentinel_for<I1, std::reverse_iterator<float*>>);
-  static_assert(!std::sized_sentinel_for<I1, std::reverse_iterator<float*>>);
----------------
Quuxplusone wrote:
> var-const wrote:
> > These don't work anymore because there is no `operator==` to compare `I1::base() == std::reverse_iterator<float*>::base()`. Previously, trying to compare the two would fail upon instantiation, but this check didn't trigger instantiation, so it could succeed (though arguably it shouldn't have).
> > 
> > If you have ideas on how to replace these checks, please let me know.
> This is one of these awful metaprogrammed tests. I don't even know what `I1` //is//, here, so I don't see why it should be a sentinel for `reverse_iterator<float*>` in particular. Deleting these lines seems great to me (and if you want to rewrite this whole test, that would be even greater ;))
> 
> To replicate the possible intent of these lines, you might add at the bottom of this test file
> ```
> static_assert( std::equality_comparable_with<std::reverse_iterator<int*>, std::reverse_iterator<const int*>>);
> static_assert(!std::equality_comparable_with<std::reverse_iterator<int*>, std::reverse_iterator<char*>>);
> static_assert( std::totally_ordered_with<std::reverse_iterator<int*>, std::reverse_iterator<const int*>>);
> static_assert(!std::totally_ordered_with<std::reverse_iterator<int*>, std::reverse_iterator<char*>>);
> static_assert( std::three_way_comparable_with<std::reverse_iterator<int*>, std::reverse_iterator<const int*>>);
> static_assert(!std::three_way_comparable_with<std::reverse_iterator<int*>, std::reverse_iterator<char*>>);
> ```
Thanks!


================
Comment at: libcxx/test/std/iterators/predef.iterators/reverse.iterators/reverse.iter.cmp/sfinae.compile.pass.cpp:67-77
+template <class T, class = decltype(std::declval<T>() == std::declval<T>())>
+std::false_type equality_comp_SFINAEs_away_impl(int);
+
+template <class T>
+std::true_type equality_comp_SFINAEs_away_impl(...);
+
+template <class T>
----------------
Quuxplusone wrote:
> Since you're in C++20-only and have concepts, please
> ```
> template<class T> concept HasEqualTo = requires (T t) { t == t; };
> template<class T> concept HasNotEqual = requires (T t) { t != t; };
> template<class T> concept HasLess = requires (T t) { t < t; };
> template<class T> concept HasLessEqual = requires (T t) { t <= t; };
> template<class T> concept HasGreater = requires (T t) { t > t; };
> template<class T> concept HasGreaterEqual = requires (T t) { t >= t; };
> template<class T> concept HasSpaceship = requires (T t) { t <=> t; };
> ```
> and then use those throughout. And you might as well test all 7 concepts for each of your types.
> 
> You know I'm not thrilled with `: IterBase`, but it seems to be saving a lot of lines of code, so I won't complain (much) this time. ;) However, doesn't this all run afoul of your `static_assert`, too? It's not legal to create the type `reverse_iterator<NoEqualityCompIter>` for at least two reasons: an iterator //must// be comparable to itself, and an iterator must also have `operator++`, which none of these do. But you needn't do anything about this yet... just wait for @CaseyCarter to tell us that it makes MSVC's implementation unhappy. ;)
Thanks for the suggestion, this looks much better now.

> It's not legal to create the type `reverse_iterator<NoEqualityCompIter>`

Yes, I think our definition of `__is_cpp17_bidirectional_iterator` is pretty lax.


================
Comment at: libcxx/test/std/iterators/predef.iterators/reverse.iterators/reverse.iter.elem/arrow.pass.cpp:78-79
 
-int main(int, char**)
-{
-    A a;
-    test(&a+1, A());
+template <class T, class = decltype(&T::operator->)>
+std::false_type bad_arrow_SFINAEs_away_impl(int);
 
----------------
Quuxplusone wrote:
> Can you find a way to use `concept`/`requires` in this file too? This one is harder because it's not C++20-and-concepts-only, but maybe with some `TEST_STD_VER` ifdefs...?  I also notice the @Mordante-irking use of `_LIBCPP_HAS_NO_CONCEPTS` below; //and// the gratuitous drive-by reformatting of `main`. I think we could kill all three birds with one stone if you just split out these new tests into `arrow.sfinae.pass.cpp` (`UNSUPPORTED: c++03, c++11, c++14, c++17`; `UNSUPPORTED: libcpp-has-no-concepts`), and then don't touch `arrow.pass.cpp` at all. Does that work for you?
I like the idea of splitting this into its own file.


================
Comment at: libcxx/test/std/iterators/predef.iterators/reverse.iterators/reverse.iter.nonmember/iter_move.pass.cpp:25-32
+template <class T>
+struct BidirectionalIteratorTraits {
+  using value_type = T;
+  using difference_type = ptrdiff_t;
+  using pointer = T*;
+  using reference = const T&;
+  using iterator_category = std::bidirectional_iterator_tag;
----------------
Quuxplusone wrote:
> Since this test is C++20-only, you don't actually need the old-school Big Five. You might not even need any of them, except probably `difference_type` because you have no `operator-`. But you definitely don't need `pointer` or `reference`, and I think not even `iterator_category`.
`reverse_iterator` uses the typedefs from `iterator_traits` for its base class (from which it still inherits even in C++20 and above if `_LIBCPP_ABI_VERSION` is less than `2` -- when compiling the tests locally, it is `1`. `iterator_traits` require either all 5 to be defined (with the exception of `pointer` in C++20) or for the iterator to satisfy `C++17Iterator` (which seems like it won't be much of a win). I got rid of `pointer`, though it doesn't change things all that much.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D120180/new/

https://reviews.llvm.org/D120180



More information about the libcxx-commits mailing list