Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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).

like image 597
463035818_is_not_a_number Avatar asked Apr 26 '26 02:04

463035818_is_not_a_number


2 Answers

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.

like image 127
Caleth Avatar answered Apr 28 '26 18:04

Caleth


I believe the answer is yes. It is undefined. In span.iterators#4, the iterator is specified to return the past-the-end value.

constexpr iterator end() const noexcept;
Returns: An iterator which is the past-the-end value.

In iterator.requirements.general#7, there is a blanket statement for "past-the-end value" (emphasis mine):

Just as a regular pointer to an array guarantees that there is a pointer value pointing past the last element of the array, so for any iterator type there is an iterator value that points past the last element of a corresponding sequence. Such a value is called a past-the-end value. Values of an iterator i for which the expression *i is defined are called dereferenceable. The library never assumes that past-the-end values are dereferenceable.

Note that the blacket statement uses very strong language and doesn't even go with the usual "unless otherwise specified" that is commonly used in other parts of the standard. So I believe it is clear that it is not allowed to dereference std::span::end().

The example (given in the answer by @Caleth) of *std::subrange {v.begin(),v.begin()+2}.end() being well-defined does not contradict the above claim because the end() is not specified in the standard to return a "past-the-end value", but that does give an answer to your original question (inspiration) about "whether there is any container (or adapter or view) whose end can be dereferenced".

like image 26
Weijun Zhou Avatar answered Apr 28 '26 17:04

Weijun Zhou