Point to previous value without decrementing pointer - c++

This is a pretty simple question.
Basically, say I have two iterators, it1 and it2. Given a value for it1, I want to define it2 to point to a location one address earlier. It would be cool if I could do it in one line, like:
vector<int>::iterator it2 = --it1;
However, this simultaneously decrements it1, so I have to re-increment it1.
vector<int>::iterator it2 = --it1;
++it1;
If these two lines are involved in a performance-intensive loop, I will have lots of it1 going back and forth for no good reason, just to define it2. On the other hand, if I do:
vector<int>::iterator it2 = it1;
--it2;
This is also slightly less than optimal as it involves two steps. Is there a way to do it in one?

You're looking for std::prev:
vector<int>::iterator it2 = std::prev(it1);
For vector's iterator, pointers and random access iterator in general, you can also use operator -:
vector<int>::iterator it2 = it - 1;

Related

How to use erase and iterator to delete a item in the two-dimension vector?

A two-dimension vector: (I want to go through vector and if I find the same name as "and", then delete or erase it, but why it can only work with it1 not it2? how to fix it?)
vector<Animals>numberOfAnimals(12);
vector<vector<Animals>>all(4,numberOfAnimals);
void delete_item(vector<vector<Animals>>&all,string ans)
{
for (auto it1=all.begin();it1!=all.end();it1++)
{
for (auto it2=(*it1).begin();it2!=(*it1).end();it2++)
{
if ((*it2).getter_name()==ans)
{
all.erase(it1); //no error
all.erase(it2--); //error
}
}
}
}
all is a vector of vectors. it1 is an iterator to all, so it can be passed to all.erase. it1 refers to one of the vectors inside all.
it2 is not an iterator to all, so it cannot be passed to all.erase. it2 is an iterator to the vector *it1 which is a vector of Animals. it2 can be passed to it1->erase. it2 refers to one of the Animals in *it1.
However, note that erasing an iterator to vector invalidates it. it1 and it2 are still used in the loop in a way that is not allowed to be done on an invalid iterator. Thus using erase in the loop without fixing the iterator has undefined behaviour.
The idiomatic way to selectively erase some elements of a container is erase-remove:
auto predicate = [&ans](const Animals& a) {
return a.getter_name()==ans;
};
for(std::vector<Animals>& v : all) {
v.erase(std::remove_if(v.begin(), v.end(), predicate));
}
This removes all of the Animals that match the predicate in each of the vectors within all.

Strange/interesting behaviour with std::vector iterators and resizing/reserving

I have two very similar bits of code; this:
std::vector<int> fail{0};
fail.reserve(2);
std::vector<int>::iterator it1 = fail.begin(), it2 = fail.begin() + 1;
fail.push_back(0);
it1 == it2;
which throws a "vector iterators incompatible" exception and this:
std::vector<int> fail{0, 0};
fail.reserve(3);
std::vector<int>::iterator it1 = fail.begin(), it2 = fail.begin() + 1;
fail.push_back(0);
it1 == it2;
which doesn't. It seems to be due to the it2 being the end of the vector in the first example but not in the second, but I'd just like to get a full clarification for why the first throws but the second doesn't.
For reference I am using MSVC.
std::vector::push_back always invalidates the past-the-end iterator, so in the first case it2. This happens regardless of resizing.
All other iterators stay intact if the vector does not reallocate, that's why they second snippet is fine.

Why no operator+ for std::list iterators?

I was about to write code like this:
std::list<whatevertype> mylist;
// ...
std::list<whatevertype>::iterator it;
for(it = mylist.begin(); it != mylist.end(); ++it) {
// ...
if(some condition)
mylist.erase(it);
}
But I realized, this code is wrong: mylist.erase(x) will invalidate the iterator it, so the ++it is likely to fail.
So I tried changing it to
std::list<whatevertype>::iterator it;
std::list<whatevertype>::iterator nextit;
for(it = mylist.begin(); it != mylist.end(); it = nextit) {
// ...
nextit = it + 1;
if(some condition)
mylist.erase(it);
}
But, to my surprise, this failed: evidently operator+ is not defined for std::list iterators.
I've since found this other question and learned that the standard idiom for deleting "out from under" an iterator is more like
for(it = mylist.begin(); it != mylist.end(); ) {
if(some condition)
it = mylist.erase(it);
else ++it;
}
I believe I could also get away with
for(it = mylist.begin(); it != mylist.end(); ) {
// ...
std::list<whatevertype>::iterator previt = it;
++it;
if(some condition)
mylist.erase(previt);
}
But my question is, is there a reason that operator+ is not defined for these iterators?
One rule they had with the std iterators and collection was to make expensive things verbose.
On a list iterator, it+50 takes O(50) time. On a vector iterator, it+50 takes O(1) time. So they implemented + on vector iterators (and other random access iterators) but not on list iterators (and other weaker iterators).
std::next and std::advance and std::prev can solve your problem easier:
auto previt = std::prev(it);
or
auto nextit = std::next(it);
these also take a count, but because they are an explicit function call it was decided that them being expensive is acceptable.
Among other things, you can search for calls to std::next and std::prev and get iterator manipulation; + is heavily overloaded and finding the expensive calls is hard.
Note that std::basic_string doesn't follow the same conventions as other std containers.
It isn't that + is missing for all iterators. It is missing for std::list iterators.
That's because a list iterator is incredibly inefficient at random access. Therefore, making random access easy is a bad idea.
You can use std::advance. It makes it more evident that you are moving across the list one element at a time.
std::list uses a BidirectionalIterator which only defines increment and decrement. As std::list is a linked list the implementation of the iterator can only move one node at a time.
The interface is designed to make sure you know that moving by more than one element isn't a simple operation like it is with other iterators like a RandomAccessIterator returned from a std::vector.
see http://en.cppreference.com/w/cpp/concept/Iterator for a definition of the different iterator types.

Determine order of set iterators

I have two iterators (say it1 and it2) into the same std::set<int>. They are obtained through lower_bound and upper_bound, therefore they are not safe to dereference (they could equal end()).
Is there an easy and safe way of telling which one goes first?
I could call std::distance(it1, it2) and std::distance(it2, it1), but that does not seem to help since if it1 != it2 then one of the calls is UB. I could test *it1 < *it2, but only if no iterator points to the end(). Finally, I could first test for the end iterator and then do the above comparison on values.
Is there an elegant solution based purely on iterators and not involving the values, i.e., dereferencing? I am willing to use up to c++14 and maybe boost.
EDIT (in reponse to comments):
I use a set because I want fast lookup and insertion, in particular much faster than linear complexity. A sorted vector would be a possible alternative, it would trivially solve the problem, but insertion and removal are linear time operations.
In my opinion, the best way is to fix your code logic and have [it1,it2) always be a valid range; if this turns out not possible (but how can it be ?), you may use something like
// O(N), forward iterators, it1, it2 should belong to range
template<class Iter>
bool precedes_or_is_equal( Iter it1, Iter it2, Iter end )
{
while( it1 != end && it1 != it2 ) ++it1;
return it1 == it2;
}

Assign value of one iterator into another iterator C++

Say I have an iterator it which is pointing to some element of map.
Also I have another iterator it1 , and I want to do something like this
it1 = it + 1;
How can we achieve this in C++ as above statement gives error in C++.
In C++11, you say auto it1 = std::next(it, 1);.
Prior to that, you have to say something like:
std::map<K, T>::iterator it1 = it;
std::advance(it1, 1);
Don't forget to #include <iterator>.