17

Generally speaking C++ doesn't allow comparing iterators between different containers. For example:

int main() {
  std::vector<int> v = {1, 2, 3};
  std::vector<int> w = {4, 5, 6};
  std::cout << v.end() == w.end() << std::endl;  // undefined!
}

But is this true also for subspans created with std::span::subspan()? For example:

int main() {
  int a[4] = { 1, 2, 3, 4};
  std::span<int> s(a);
  std::span<int> t = s.subspan(1, 2);
  std::cout << t.begin() - s.begin() << std::endl;
}

This prints 1, which is expected, because internally iterators are probably just pointer to the underlying array. The question is: does the standard guarantee that this works correctly?

And if so, more generally, can I also compare iterators from any spans that come from the same contiguous object in memory? For example:

int main() {
  int a[5] = { 1, 2, 3, 4, 5};
  std::span<int> s(a);
  std::cout << (s.subspan(1, 1).end() < s.subspan(3, 1).begin()) << std::endl;
}
4
  • 3
    Added language-lawyer tag, per "does the standard guarantee" Commented Jul 26, 2023 at 16:37
  • 1
    Relevant draft section: 24.7.2.2.7 / span.iterators Commented Jul 26, 2023 at 16:44
  • Compare iterators on different objets is a U.B. Trying this code with MSVC, gives the error "cannot compare incompatible span iterators" in debug, but because their are raw pointers in release the code runs fine. Commented Jul 26, 2023 at 19:39
  • 3
    Another interesting question would be whether subspan iterators can be compared to iterators from the underlying container Commented Jul 26, 2023 at 19:58

1 Answer 1

5

I don't think the standard allows this.

According to the description of Cpp17ForwardIterator in [forward.iterators]:

The domain of == for forward iterators is that of iterators over the same underlying sequence.

This means that the equality and inequality operations of two iterators are only defined over the same underlying sequence.

In your example, the underlying sequences of the two iterators being compared are different, because they have different start points and different sizes, which makes comparisons no longer well-defined when one of the iterators is outside the bounds of the other sequence.


As mentioned in the comments, your example will trigger an assert in MSVC-STL's debug mode, which was explained by the maintainer in #1435. Similar issues are also discussed in the Cpp Core Guidelines, see #1157.

Sign up to request clarification or add additional context in comments.

4 Comments

Does std::ranges::subrange suffer from the same issue as std::span::subspan? I am not sure whether I should ask a new question, but occasionally I want to do std::prev(subrange.begin()) when I know that subrange.begin() does not equal the begin() of underlying range. Is that allowed?
I think std::prev(subrange.begin()) is ok, because subrange.begin() is specified to return the underlying original iterator. The validity of the operation on the original iterator will not change.
"underlying sequences of the two iterators being compared are different" - that requires citation. It is plausible that the wording is that span's underlying sequence remains the original buffer, with begin/end of the span itself being a subsequence. It would depend on how the standard is worded.
@Yakk-AdamNevraumont I don't think the current standard clearly defines what the underlying sequence of an iterator is. There was one in C++17: "An iterator j is called reachable from an iterator i if and only if there is a finite sequence of applications of the expression ++i that makes i == j. If j is reachable from i, they refer to elements of the same sequence" but I think it could still be interpreted differently in the current context.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.