Is the comparison of begin() and end() iterators of two std::spans that point to the same memory well defined?
#include <span>
#include <cassert>
int main() {
int arr[5]{1,2,3,4,5};
std::span s1{arr};
std::span s2{arr};
assert(s1.begin() == s2.begin());
assert(s1.end() == s2.end());
assert(s1.begin() + s1.size() == s2.end());
}
All asserts pass on all implementations of std::span so far, but is there anything I may be missing that makes this a bug, e.g., UB?
For context, this can arise if you have a class that tries to hide its internals with a span.
class some_class
{
public:
std::span<int> vec_view() { return vec; }
private:
std::vector<int> vec;
};
int main() {
some_class c;
std::for_each(c.vec_view().begin(), c.vec_view().end(), [](auto&){});
}
This is related to Does C++ allow comparison between std::span::iterators when one span is a subspan of the other?, but this question is not about a std::subspan, and moreover the std::span version also passes MSVC asserts, unlike the version with the std::subspan.
Is comparison of
begin()andend()iterators of twostd::spans that point to the same memory well-defined?
Who knows. std::span::iterator models contiguous_iterator ([span.iterators]), and therefore, forward_iterator.
[iterator.concept.forward] paragraph 2 applies:
The domain of
==for forward iterators is that of iterators over the same underlying sequence. However, value-initialized iterators [...].
The C++ standard never defines what an "underlying sequence" is.
.begin() and .end() on, then std::span(...).begin() == std::span(...).begin() is undefined. no matter what.operator* on the iterators, then s1.begin() == s2.begin() has to be true.The first option is needlessly restrictive for non-owning ranges like std::string_view and std::span.
The second option may have unintended consequences, like being able to compare iterators from entirely different std::ranges::filter_views created with a function pointer as a predicate (same iterator type, same elements, but comparison is nonsensical).
Some language design decision will need to be made here, possibly case-by-case.
As you've already pointed out, there is an active LWG Issue 3989,
and until that is resolved, there won't be a definitive answer to this question, even for two std::spans that are equivalent, not just subspans.
All asserts pass on all implementations of std::span so far, but is there anything I may be missing that makes this a bug, e.g., UB?
I don't think this would be undefined behavior. After all, std::span is just a class, not part of the core language, so I think this should only be unspecified behavior.
span<ElementType, Extent> is a trivially copyable type.
For a span s, any operation that invalidates a pointer in the range [s.data(), s.data() + s.size()) invalidates pointers, iterators, and references to elements of s.
A span::iterator is not allowed to hold a reference to the source span, so in addition to a pointer to the referenced element, it can only hold two additional pieces of information: span.data() and span.size().
Therefore, as long as span1.data() == span2.data() && span1.size() == span2.size(), it is guaranteed that iterators from different spans can be compared.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With