I'm trying to implement my own list
and map
, and I've been having trouble with the end()
method since these containers aren't contiguous in memory.
After some research, I've ended up implementing the end()
as a method that returns an iterator that holds nullptr
. (In what way does end() point to 'one past the end' in non-contiguous containers?). That is enough for most of my implementation, as it verifies that list::begin() == list::end()
when list is empty (empty list has no nodes so head
points to nullptr
).
However, I've seen that it's possible to call operator--()
on past-the-end iterators to point to the actual last element of the container.
int main() {
{
std::map<int, char> m = {{1, 'a'}, {2, 'b'}, {3, 'c'}};
auto it = m.end();
--it;
std::cout << it->first << "\n"; // output: 3
std::cout << it->second << "\n"; // output: c
}
{
std::list<int> l{ 1, 2, 8};
auto it = l.end();
--it;
std::cout << *it << "\n"; // output: 8
}
}
How is this achieved? The implementation of operator--()
of _List_iterator
in the gcc changes the current node to point to the previous one:
_Self&
operator--() _GLIBCXX_NOEXCEPT
{
_M_node = _M_node->_M_prev;
return *this;
}
But how is this possible if the node is out of the list? How does that node even exist? Does this mean that an extra node is allocated? Are you actually allowed to call operator--()
to a past-the-end iterator?
These are the begin()
and end()
methods of gcc's std::list
, in case it helps:
/**
* Returns a read-only (constant) iterator that points to the
* first element in the %list. Iteration is done in ordinary
* element order.
*/
_GLIBCXX_NODISCARD
const_iterator
begin() const _GLIBCXX_NOEXCEPT
{ return const_iterator(this->_M_impl._M_node._M_next); }
/**
* Returns a read/write iterator that points one past the last
* element in the %list. Iteration is done in ordinary element
* order.
*/
_GLIBCXX_NODISCARD
iterator
end() _GLIBCXX_NOEXCEPT
{ return iterator(&this->_M_impl._M_node); }