I have a chunk of C code that is supposed to go through a vector and delete reoccurring objects in-place. I completed the task (C1) using an iterator. I continued with the problem and wanted to do it using range-based for loop like the latter (C2). However, I ran into complier errors. Can someone explain why these two codes do not function the same and if there is a way of accessing the range-for-loop's internal iterator. C1)
for(auto itr = nums.begin(); itr != nums.end(); itr ) {
if(*itr != set) {
set = *itr;
} else {
nums.erase(itr--);
}
}
C2)
for(auto num : nums) {
if(num != set) {
set = num;
} else {
nums.erase((&num));
}
}
CodePudding user response:
Can someone explain why these two codes do not function the same
Because you do different things. std::vector::erase
function takes iterators as arguments - special objects which refer to specific position within a vector. You can dereference an iterator because it has dereference operator overloaded, however (&num)
doesn't turn the num
into the iterator, it's still a pointer, which is just not compatible with std::vector::erase
function parameters.
if there is a way of accessing the range-for-loop's internal iterator
Not really. You are trying to alter a container which you are currently going through, that is error-prone on its own. For iterator it works only because you invalidate and update your iterator on each call to erase:
nums.erase(itr--);
itr--
makes the iterator to point to the previous value and returns the iterator which points to current one (so you can remove the result of this operation freely, because it's now a temporary value, and your local iterator variable itr
no longer points to this position)
CodePudding user response:
The two loops you have provided are not equivalent. The first loops across iterators to elements in the vector. The second loops across the values stored in the vector. For a vector of integers, the type for num
is integer. The erase method for vectors requires either an iterator or a pair of iterators to mark the elements to remove. So even &num
will not work as it is not providing an iterator.
For reference:
https://en.cppreference.com/w/cpp/container/vector/erase
https://en.cppreference.com/w/cpp/language/range-for
Consider using std::unique
:
https://en.cppreference.com/w/cpp/algorithm/unique
CodePudding user response:
It would be more correctly to rewrite the first code snippet at least like
for ( auto itr = nums.begin(); itr != nums.end(); ) {
if(*itr != set) {
set = *itr ;
} else {
itr = nums.erase( itr );
}
}
After erasing an element iterators starting from the iterator of the erased element become invalid.
So this range-based for loop
for(auto num : nums) {
if(num != set) {
set = num;
} else {
nums.erase((&num));
}
}
invokes undefined behavior because it is based on iterators. Moreover in modern C standard libraries iterators of the class template std::vector
are not pointers. So moreover this statement
nums.erase((&num));
is just invalid. You shall not rely on that iterators of the class template std::vector
are defined as pointers.
Pay attention to that if you want to remove adjacent equal elements then you could use the standard algorithm std::unique as for example
nums.erase( std::unique( nums.begin(), nums.end() ), nums.end() );
Here is a demonstration program.
#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>
int main()
{
std::vector<int> nums = { 1, 2, 2, 3, 3, 3, 4, 4, 4, 4 };
for ( const auto &item :nums )
{
std::cout << item << ' ';
}
std::cout << '\n';
nums.erase( std::unique( std::begin( nums ), std::end( nums ) ),
std::end( nums ) );
for ( const auto &item :nums )
{
std::cout << item << ' ';
}
std::cout << '\n';
}
The program output is
1 2 2 3 3 3 4 4 4 4
1 2 3 4