Skip to content
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

[libc++] std::prev(it) should not compile for a non-bidi iterator #109456

Closed
ldionne opened this issue Sep 20, 2024 · 6 comments · Fixed by #112102
Closed

[libc++] std::prev(it) should not compile for a non-bidi iterator #109456

ldionne opened this issue Sep 20, 2024 · 6 comments · Fixed by #112102
Assignees
Labels
libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.

Comments

@ldionne
Copy link
Member

ldionne commented Sep 20, 2024

std::prev(it, n) could conceivably be well-formed for a non-bidirectional it if n is negative, however std::prev(it) is always UB, and we can easily diagnose it at compile-time. Right now std::prev(non_bidi) is a no-op, which is absolutely vexing.

https://godbolt.org/z/zaG3jTEqP

Potential patch:

diff --git a/libcxx/include/__iterator/prev.h b/libcxx/include/__iterator/prev.h
index e950d8dc4147..98883188312a 100644
--- a/libcxx/include/__iterator/prev.h
+++ b/libcxx/include/__iterator/prev.h
@@ -26,7 +26,7 @@ _LIBCPP_BEGIN_NAMESPACE_STD
 
 template <class _InputIter, __enable_if_t<__has_input_iterator_category<_InputIter>::value, int> = 0>
 inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX17 _InputIter
-prev(_InputIter __x, typename iterator_traits<_InputIter>::difference_type __n = 1) {
+prev(_InputIter __x, typename iterator_traits<_InputIter>::difference_type __n) {
   // Calling `advance` with a negative value on a non-bidirectional iterator is a no-op in the current implementation.
   // Note that this check duplicates the similar check in `std::advance`.
   _LIBCPP_ASSERT_PEDANTIC(__n <= 0 || __has_bidirectional_iterator_category<_InputIter>::value,
@@ -35,6 +35,12 @@ prev(_InputIter __x, typename iterator_traits<_InputIter>::difference_type __n =
   return __x;
 }
 
+template <class _BidirectionalIterator,
+          __enable_if_t<__has_bidirectional_iterator_category<_BidirectionalIterator>::value, int> = 0>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX17 _BidirectionalIterator prev(_BidirectionalIterator __it) {
+  return std::prev(std::move(__it), 1);
+}
+
 #if _LIBCPP_STD_VER >= 20
 
 // [range.iter.op.prev]
@ldionne ldionne added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Sep 20, 2024
@philnik777
Copy link
Contributor

I think it would be better to static_assert here, since this isn't a constraint.

@frederick-vs-ja
Copy link
Contributor

There's LWG3197 for this, which is still open now.

@ldionne
Copy link
Member Author

ldionne commented Sep 23, 2024

I think it would be better to static_assert here, since this isn't a constraint.

Per the LWG issue mentioned by @frederick-vs-ja , it seems like it's not clear to everyone whether there is a constraint or not.

IMO this should definitely be a constraint, prev implies going backwards, and I would argue that 99% of the usage of std::prev with a non-bidirectional iterator is probably unintended.

@ldionne
Copy link
Member Author

ldionne commented Sep 23, 2024

CC @jwakely perhaps we want to bring back that LWG issue to the table?

@jwakely
Copy link
Contributor

jwakely commented Sep 23, 2024

It's been on the table for five years 😄 apart from the note I added a few months ago nobody had made a case for why we should prefer any of the options.

@ldionne
Copy link
Member Author

ldionne commented Sep 23, 2024

I would argue for option (A) or option (C) in LWG3197, otherwise the result is that calling std::prev(non_bidi) has undefined behavior. Since it's so easy to catch and I think we can agree that most users don't intend to call std::prev to go forward, we're likely going to catch unintended misuse without preventing any significant intended use.

(That's me trying to make a case BTW 🙂)

ldionne pushed a commit that referenced this issue Oct 23, 2024
The std::prev function appeared to work on non Cpp17BidirectionalIterators, however it
behaved strangely by being the identity function. That was extremely misleading and
potentially dangerous, since several recent iterators that are C++20 bidirectional_iterators
don't satisfy the Cpp17BidirectionalIterator named requirement. For example:

    auto zv = std::views::zip(vec1, vec2);
    auto it = zv.begin() + 5;
    auto it2 = std::prev(it); // "it2" will be the same as "it",  instead of the previous one

Here, zip_view::iterator is a c++20 random_access_iterator, but it only satisfies the
Cpp17InputIterator named requirement because its reference type is a proxy type. Hence
`std::prev` would silently accept that iterator but it would do a no-op instead of going
to the previous position.

This patch changes `std::prev(it)` to produce an error at compile-time when instantiated
with a type that is not a Cpp17BidirectionalIterator.

Fixes #109456
NoumanAmir657 pushed a commit to NoumanAmir657/llvm-project that referenced this issue Nov 4, 2024
The std::prev function appeared to work on non Cpp17BidirectionalIterators, however it
behaved strangely by being the identity function. That was extremely misleading and
potentially dangerous, since several recent iterators that are C++20 bidirectional_iterators
don't satisfy the Cpp17BidirectionalIterator named requirement. For example:

    auto zv = std::views::zip(vec1, vec2);
    auto it = zv.begin() + 5;
    auto it2 = std::prev(it); // "it2" will be the same as "it",  instead of the previous one

Here, zip_view::iterator is a c++20 random_access_iterator, but it only satisfies the
Cpp17InputIterator named requirement because its reference type is a proxy type. Hence
`std::prev` would silently accept that iterator but it would do a no-op instead of going
to the previous position.

This patch changes `std::prev(it)` to produce an error at compile-time when instantiated
with a type that is not a Cpp17BidirectionalIterator.

Fixes llvm#109456
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.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants