Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

STL iterator before std::map::begin()

In C++11's std::map, is there some valid iterator x such that ++x is guaranteed to equal map::begin()? I would like to detect if a function I just called (mine) has walked an iterator off the front of a function. The function will move the iterator exactly one position backward.

Does the answer hold for the rest of the library?

like image 834
George Hilliard Avatar asked Oct 17 '13 02:10

George Hilliard


People also ask

How do I iterate a map in STL?

Iterating over a map by using STL Iterator:By creating an iterator of std::map and initializing it to the starting of map and visiting upto the end of map we can successfully iterate over all the elements of map.

Does MAP return an iterator?

map() loops over the items of an input iterable (or iterables) and returns an iterator that results from applying a transformation function to every item in the original input iterable.

How do I get the first element of a map in CPP?

map::begin() begin() function is used to return an iterator pointing to the first element of the map container. begin() function returns a bidirectional iterator to the first element of the container.

What is the complexity of std::map :: insert () method?

Time complexity: k*log(n) where n is size of map, k is no. of elements inserted.


2 Answers

It's very important to realize that Standard Library containers are semi-open ranges [begin, end), i.e. you can iterate one-past-the-end. For bidirectional (and random) iterators you can also do --end() and come back from the brink. Dereferencing one-past-the-end by *end() is undefined behavior, and so is decrementing the begin iterator by --begin() or begin() - 1. There is only one exception to this: std::forward_list which has a non-dereferenceable iterator before_begin() that satisfies ++before_begin() == begin() (but note that for a forward_list you cannot decrement begin() either).

This fundamental asymmetry for bidirectional iterators means that reverse iterators are thin wrappers around regular iterators. In most Standard Library implementations they simply contain a copy base_ of the underyling iterator. Incrementing a std::reverse_iterator calls something like --base_; return *this;, and dereferencing it does auto old = base_; return *--old;. At no point is the underlying iterator decremented to before begin(), and no dereferencing of end() is done that way.

Below are the four ways to iterate over a container supporting bidirectional or random iterators, and the relations between the various iterators (.base() converts a std::reverse_iterator back to its underlying iterator)

#include <iomanip>
#include <iostream>
#include <iterator>
#include <map>
#include <string>

int main()
{    
    auto c = std::map<int, std::string>{ {1, "hello"}, {2, "world"} };
    
    {   // 1) forward iteratation
        auto it = begin(c);
        for (; it != end(c); ++it){}
        std::cout << std::boolalpha << (it == c.rbegin().base()) << "\n";
    }

    {   // 2) meh, backward iteration
        auto it = end(c) - 1; //end return iterator after the last element.
        for (; it != begin(c); --it){}
        std::cout << std::boolalpha << (it == c.rend().base()) << "\n";
    }

    {   // 2') better: reverse iteration
        auto it = c.rbegin();
        for (; it != c.rend(); ++it){}
        std::cout << std::boolalpha << (it.base() == begin(c)) << "\n";
    }

    {   // 1') backward reverse, better avoid this
        auto it = c.rend();
        for (; it != c.rbegin(); --it){}
        std::cout << std::boolalpha << (it.base() == end(c)) << "\n";
    }
}

Live Example

If you have data structure that should support bidirectional iteration but there are no member iterators .rbegin() or rend(), you can easily define them yourself by std::reverse_iterator(end()) and std::reverse_iterator(begin()), respectively (this is also the way the Standard Library usually implements them).

like image 51
TemplateRex Avatar answered Oct 22 '22 23:10

TemplateRex


No, iterators before the beginning in std containers are all UB (except for reverse iterators, which will probably not solve your problem).

You probably need to fix the function in question. Failing that, wrap it and catch the bad behavior before you call it. Failing that, you could insert a negative infinity element into the map key type ordering, and add a sentinal value. Failing that, you could write iterator adapters that wrap your map iterators with ones that can go one-before-beginning without UB.

These are ordered in my order of recommendation, roughly. Each has ways it could fail, and they get more error prone and dangerous as my recommendation gets more remote.

like image 45
Yakk - Adam Nevraumont Avatar answered Oct 22 '22 21:10

Yakk - Adam Nevraumont