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.