c++iteratorlanguage-lawyerundefined-behaviorstd-span

Is dereferencing std::span::end always undefined?


While browsing cppreference to see if there is any container (or adapter or view) whose end can be dereferenced, I stumbled upon std::span::end. The article has the usual note saying:

Returns an iterator to the element following the last element of the span.

This element acts as a placeholder; attempting to access it results in undefined behavior.

However, std::span does not own its elements. std::span::end does not necessarily refer to one past the last element of the actual container.

Does this code invoke undefined behavior?

#include <vector>
#include <span>
#include <iostream>

int main() {
    std::vector<int> v{1,2,3,4,5};
    std::span<int> s{v.begin(),v.begin()+2};
    std::cout << *(s.end());
}

With a naive implementation it would work just fine. Though, an implementation might make assumptions (eg "end is never dereferenced") that would break the above code. Hence, the question is whether the standard formally declares this as UB (or if its a mistake in the cppreference article).


Solution

  • I think it is implementation-defined as to whether dereferencing std::span::end() is always unsafe or sometimes defined behaviour. Pedantically that isn't the same as undefined behaviour, but it is very close.

    std::span<T>::iterator is an implementation defined type that satisfies std::contiguous_iterator etc, it need not be exactly T*.

    An implementation could choose to check on dereference that you are in range. If it does, then your deference could throw (or whatever).

    Instead, if the implementation chooses to use T* (or a class that wraps it and does no checking) as std::span<T>::iterator, then your dereference is valid, as it's a pointer value that we know is dereferenceable.

    In contrast, if your example used std::subrange<std::vector<int>::iterator, std::vector<int>::iterator> s{v.begin(),v.begin()+2};, then it would be defined behaviour, as std::subrange::end() returns the sentinel value, which is a dereferenceable std::vector<int>::iterator.