Can I increment an iterator by just adding a number?

Can I do normal computations with iterators, i.e. just increment it by adding a number?

As an example, if I want to remove the element vec[3], can I just do this:

std::vector<int> vec;
for(int i = 0; i < 5; ++i){
      vec.push_back(i);
}
vec.erase(vec.begin() + 3); // removes vec[3] element

It works for me (g++), but I'm not sure if it is guaranteed to work.


Solution 1:

It works if the iterator is a random access iterator, which vector's iterators are (see reference). The STL function std::advance can be used to advance a generic iterator, but since it doesn't return the iterator, I tend use + if available because it looks cleaner.

C++11 note

Now there is std::next and std::prev, which do return the iterator, so if you are working in template land you can use them to advance a generic iterator and still have clean code.

Solution 2:

A subtle point is that the operator+ takes a Distance; i.e., a signed integer. If you increment the iterator by an unsigned, you may lose precision and run into a surprise. For example on a 64 bit system,

std::size_t n = (1 << 64) - 2;
std::vector<double> vec(1 << 64);
std::vector<double> slice(vec.begin() + n, vec.end());

leads to implementation-defined behavior. With g++ or clang, you can ask the compiler to warn you about such undesired conversions with the warning flag -Wsign-conversion that is not part of the canonical -Wall or -Wextra.

A work-around is to work on the pointer directly

std::vector<double> slice(vec.data() + n, vec.data() + vec.size());

It's not pretty but correct. In some occasions, you need to construct the iterator manually, for example

std::vector<double>::iterator fromHere{vec.data() + n};
vec.erase(fromHere, vec.end());

Solution 3:

It works with random access iterators. In general you may want to look at std::advance which is more generic. Just be sure to understand performance implications of using this function template.

Solution 4:

Number arithmetic is possible only with random access iterators such as those in std::vector and std::deque.

    std::vector<int>list={1,2,3,4,5,6,7,8};
    auto last_v=*(list.end()-1);
    auto third_last_v=*(list.end()-3);
    std::cout<<"Last & 3rd last entry for vector:"<<last_v<<","<<third_last_v<<std::endl;

will output 8 and 6, however for std::map, std::multimap, std::set, std::multiset having bidirectional iterator

    std::map<int,std::string> map_={{1,"one"},{2,"two"},{3,"three"}};
    auto last_mp=*(map_.end()-1);
    auto third_last_mp=*(map_.end()-3);
    std::cout<<"Last & 3rd last entry for map:("<<last_mp.first<<","<<last_mp.second<<") and ("<<third_last_mp.first<<","<<third_last_mp.second<<")"<<std::endl;

will result in error: no match for ‘operator-’ (operand types are ‘std::map, int>::iterator {aka std::_Rb_tree_iterator, int> >}’ and ‘int’)

For bidirectional iterator std::next(),std::prev or std::advance() works

    std::map<int,std::string> map_={{1,"one"},{2,"two"},{3,"three"}};
    auto last_mp=*std::prev(map_.end(),1);
    auto third_last_mp=*std::prev(map_.end(),3);
    std::cout<<"Last & 3rd last entry for map:("<<last_mp.first<<","<<last_mp.second<<") and ("<<third_last_mp.first<<","<<third_last_mp.second<<")"<<std::endl;

will output (3,three) and (1,one). On a similar note std::unordered_map has a forward iterator, so here std::next() makes sense to use.