admin 管理员组

文章数量: 1086019

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

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

Share Improve this question edited Mar 27 at 14:45 Weijun Zhou 5,1662 gold badges21 silver badges41 bronze badges asked Mar 27 at 9:28 463035818_is_not_an_ai463035818_is_not_an_ai 124k11 gold badges102 silver badges214 bronze badges 9
  • 3 I would say that implementer might add checks on span::iterator – Jarod42 Commented Mar 27 at 9:31
  • 2 See en.cppreference/w/cpp/container/span/end : 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.. So it is not ok to use it (even if it seems to work). – Pepijn Kramer Commented Mar 27 at 10:49
  • @PepijnKramer is there a standard reference for that? – Caleth Commented Mar 27 at 10:51
  • 1 If I made a paranoia version of the standard C++ library that did all the (potentially expensive) things to verify and validate everything everywhere all at once, and I added code in my "fat pointer" span to std::abort if any attempt was made to dereference it at end (or anywhere outside of it's designated valid domain), that would still comply with the standard. (Any undefined behavior becoming std::abort in my fantasy paranoia standard C++ library.) – Eljay Commented Mar 27 at 11:06
  • 1 "Undefined behavior" doesn't mean "something bad will happen". It means only that the C++ language definition doesn't tell you what the code does. – Pete Becker Commented Mar 27 at 13:13
 |  Show 4 more comments

2 Answers 2

Reset to default 6

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.

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

本文标签: cIs dereferencing stdspanend always undefinedStack Overflow