Can I increment an iterator by just adding a number?

Viewed 52940

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.

4 Answers

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.

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());

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.

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.

Related