3

According to the answers in this and this question, the C++ standard states in § 23.2.1 that end() is of constant time complexity for all stl containers.

If I am understanding correctly:

  1. std::forward_list only knows about it's first element, and each of the list entries only know about the next element.
  2. lists are non-contiguous in memory
  3. a.begin() == a.end() is true for empty containers a
  4. end() is supposed to be an iterator pointing to 'one past the end of a container'

So while working on some loops over forward_lists, I was wondering:

How can end() be of constant time complexity (i.e. not advance to 'one past the end') in case of forward_list?

I looked in forward_list.cpp and found the declaration

iterator       end() _NOEXCEPT
    {return       iterator(nullptr);}

That makes sense for the constant time requirement, but not for the - admittedly vage - rule corresponding to point 4 above.

So some questions remain:

  • What is 'one past the end' supposed to mean for non-contiguous storage?
  • How does nullptr fit the definition of 'one past the end'?
  • How is MyForwardList.begin() == MyForwardList.end() true if MyForwardList is empty?
  • Why isn't end() always defined as nullptr?
mrclng
  • 483
  • 2
  • 14
  • 3
    Your understanding is wrong at point 4. Obviously, for node-based containers there can be no concept of "one past the end". –  Jun 26 '17 at 19:36
  • Basically `end()` can return some sort of sentinel value for the iterator that the class can recognize as signalling something "past the end of the last item in the container". If a container's iterator were a simple raw pointer, then `end()` might return a null pointer or a pointer with all bits set for example. – Michael Burr Jun 26 '17 at 19:44
  • @NeilButterworth: I disagree-ish. With any range of iterators, if you iterate to the last element, then increment one more time, you're at the "end" iterator. This is "one past the last element" – Mooing Duck Jun 26 '17 at 19:48
  • @Mooing I meant that for contiguous containers there actually is something for past the end iterators to refer to (and there is in C too). –  Jun 26 '17 at 19:52

4 Answers4

6

What is one past the end supposed to mean for non-contiguous storage?

It means whatever you'd get if you incremented an iterator to the last element.

How does nullptr fit the definition of 'one past the end'?

If that's what you get if you increment an iterator to the last element, then it fits the definition.

How is MyForwardList.begin() == MyForwardList.end() true if MyForwardList is empty?

For an empty list, they both return the same thing. Likely an "empty" iterator.

Why isn't end() always defined as nullptr?

Because sometimes that's not the most convenient way to define it, and so long as you meet the requirements, you can implement it however you want.

It's basically just a circular definition. The end function returns whatever you'd get if you took an iterator to the last element in the list and incremented it or, for an empty list, returns the same thing begin returns. So long as all these relationships hold, everything works, regardless of what internal values or logic you use to guarantee the relationships.

David Schwartz
  • 179,497
  • 17
  • 214
  • 278
5

"One past the end" is defined in terms of iterator traversal, not memory locations. An end iterator is what you get when you take an iterator to the last element and increment it (or begin an empty container).

std::forward_list's end iterator pointing at nullptr makes sense: in the implementation the last node probably has a nullptr next node, and following that link yields indeed the end iterator. std::vector's end might physically point past the storage for the same reason.

Quentin
  • 62,093
  • 7
  • 131
  • 191
5

end() is supposed to be an iterator pointing to 'one past the end of a container'.

That's subtly different than what the standard says (emphasis mine):

begin() returns an iterator referring to the first element in the container. end() returns an iterator which is the past-the-end value for the container. If the container is empty, then begin() == end();

The standard says "past-the-end", not "one past-the-end".

Implementors are free to chose whatever implementation of "past-the-end" works for a particular container type.

R Sahu
  • 204,454
  • 14
  • 159
  • 270
2

What is 'one past the end' supposed to mean for non-contiguous storage?

Simply the node that the last valid node points to.

How does nullptr fit the definition of 'one past the end'?

Well, when you build a node based data structure the last valid node will point to nullptr for the next node, that is how you know your at the end.

How is MyForwardList.begin() == MyForwardList.end() true if MyForwardList is empty?

In an empty list the head is nullptr or the head points to nullptr so comparing nullptr to nullptr is true.

Why isn't end() always defined as nullptr?

Because there is no requirement that your using pointers.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402