I have a wrapper for std::vector and I've implemented function that replaces a section within a vector with another vector. I've tried to put the assignment of an iterator directly in the if condition and got unexpected results.
I'm using Visual Studio 2013 and with FAIL defined I get Debug Assertion Failed! - vector iterators incompatible. Is it possible that the condition is being evaluated from right to left? I can't get over it.
This is a (poorly implemented) code that reproduces my problem - meant to replace 3rd and 4th elements of vec with 1st and 2nd elements of vec_second:
#include <iostream>
#include <vector>
#include <iterator>
using std::cout;
//#define FAIL
int main()
{
std::vector<int> vec = {1, 2, 3, 4, 5};
std::vector<int> vec_second = {6, 7};
auto it = vec.begin() + 2;
#ifndef FAIL
it = vec.erase(it, it + vec_second.size());
if(it == vec.end())
#else
if((it = vec.erase(it, it + vec_second.size())) == vec.end())
#endif
vec.reserve(vec.size() + vec_second.size());
vec.insert(it, vec_second.begin(), vec_second.end());
for(auto const& x : vec)
cout << x << " ";
}
But runs fine on Coliru's GCC.
if((it = vec.erase(it, it + vec_second.size())) == vec.end())
Since there is no sequence point between them, a compiler is free to call erase and end in either order. If end gets called first, that iterator is immediately invalidated by erase, leading to undefined behavior.
Your #ifndef FAIL code is the safe way to do this.
Related
So I need to erase elements from a std::set in a particular order, doing something with the first.
so if I had a set containing {1,2,3,4,5,6} and my I wanted to go until 4, I need to:
doSomething(6);
erase(6);
doSomething(5);
erase(5);
doSomething(4);
erase(4);
I have the following code that does not work:
#include <iostream>
#include <set>
void doSomething(int value) {
std::cout << value << '\n';
}
int main() {
std::set<int> s = {1,2,3,4,5,6};
auto beginIt = s.end();
auto endIt = s.lower_bound(4);
auto rbegin = std::make_reverse_iterator(beginIt);
auto rend = std::make_reverse_iterator(endIt);
for (auto it = rbegin; it != rend;) {
doSomething(*it);
s.erase(std::next(it).base());
}
return 0;
}
I think the issue is that it erasing the end iterator then keeps going util it crashes.
How can I get this to work.
godbolt: https://godbolt.org/z/KvaGWhr4G
The correct way of doing what you want is to getting your end iterator each time.
for (auto it = rbegin; it != std::make_reverse_iterator(s.lower_bound(4));) {
doSomething(*it);
s.erase(std::next(it).base());
}
Now let's see why you initial code didn't work.
In set, the iterators are not invalidated after erasing an element, EXCEPT for the iterator that was pointing to the erased element.
Now let's see what happened in the last iteration when you remove 4.
When dereferencing the rend, we will see that it points to 3. However, the base of rend points to 4. And after removal of 4, the base of rend has been invalidated. So your program had Undefined behavior.
To understand why getting end iterator at every iteration works, we have to understand that during the program the base of it is always s.end(). And at the last step, when we call s.lower_bound(4), we get s.end(). Hence, the condition for exiting the loop is satisfied.
I am using remove() of std::list to remove elements in a for loop. But it is creating segmentation fault. I am not using iterators. Program is given below.
#include <iostream>
#include <list>
using namespace std;
int main() {
list <int> li = {1, 2, 3, 4, 5};
for(auto x : li)
{
if (x == 4) {
li.remove(x);
}
}
return 0;
}
In case of iterators, I understand that iterators are invalidated if we remove an element and we need to take care of incrementing iterator properly. But here I am not using iterators and I am using remove() which doesn't return any. Can any one please let me know if we can't use remove in a loop or if there is any issue with the code.
But here I am not using iterators and I am using remove() which doesn't return any. Can any one please let me know if we can't use remove in a loop or if there is any issue with the code.
You are mistaken. Range-based for loops are based on iterators. So after this call:
li.remove(x);
The current used iterator becomes invalid.
For example, in C++17, the range-based for loop is defined in the following way (9.5.4 The range-based for statement):
1 The range-based for statement
for ( for-range-declaration : for-range-initializer ) statement
is equivalent to
{
auto &&__range = for-range-initializer ;
auto __begin = begin-expr ;
auto __end = end-expr ;
for ( ; __begin != __end; ++__begin ) {
for-range-declaration = *__begin;
statement
}
}
Pay attention to that. Using the range-based for loop to call the member function remove() does not make a great sense, because the function will remove all elements with the given value.
If the compiler supports C++20, you can just write:
std::erase( li, 4 );
Instead of walking the list and removing elements alongside, what can cause invalidation of iterators, you can use the erase-remove idiom:
[Demo]
#include <algorithm> // remove_if
#include <fmt/ranges.h>
#include <list>
int main() {
std::list<int> li = {1, 2, 3, 4, 5};
li.erase(
std::remove_if(std::begin(li), std::end(li), [](int i){ return i == 4; }),
std::end(li)
);
fmt::print("{}", li);
}
// Outputs:
//
// [1, 2, 3, 5]
if you want to do it with for loop, this might be an answer.
#include <list>
int main() {
std::list<int> li = { 1, 2, 3, 4, 5 };
for (auto iter = li.begin(); iter != li.end();)
{
if (*iter == 4)
iter = li.erase(iter);
else
++iter;
}
return 0;
}
erase returns next iterator after erase the element.
This is valid for all stl containers like vector, map, etc...
Yes, you're using iterators: a range-based for loop uses iterators.
See the Explanation section of https://en.cppreference.com/w/cpp/language/range-for
If the elements in your std::list are unique (as they appear in your example) or if you wish to only remove the first one, add a break; after your remove call.
The range-based-for iterator will be invalidated but inconsequentially so if never used afterwards.
Is it valid to call operator-- on an iterator that already points to the first element of the collection? Does the answer change for different collections (e.g. list vs vector vs set). E.g. see below
#include <algorithm>
#include <iostream>
#include <numeric>
#include <string>
#include <vector>
int main()
{
std::vector<int> v {1, 2, 4, 8, 16};
auto i=v.begin();
auto j=i;
--i; // not sure what's the effect of this
v.erase(j);
++i; // also not sure if this is not std::begin() anymore, what's the effect of ++
v.erase(i);
// Print vector.
std::for_each(v.begin(), v.end(), [](const int n) { std::cout << n << ' '; });
}
I suspect it's undefined behaviour but not quite sure.
Furthermore, what about removing elements from a std::list like below
std::list<int> v = { 1, 2, 3, 4, 5, 6 };
for (auto it = v.begin(); it != v.end(); it++)
{
v.erase(it--);
}
Let's take std::list as an example, because essentially the same reasoning will apply to the other containers.
Looking at the member types of std::list, we see that std::list::iterator is a LegacyBidirectionalIterator. Checking the description there, we see the following precondition listed for operator-- to be valid:
Preconditions:
a is decrementable (there exists such b that a == ++b)
This is not the case for an iterator to the first element in a container, and indeed cppreference explicitly calls this out:
The begin iterator is not decrementable and the behavior is undefined if --container.begin() is evaluated.
Other containers like std::vector use more expansive notions like LegacyRandomAccessIterator, but there's nothing there that changes the behavior of decrementing a begin iterator.
There is a zip_with function provided by Eric Niebler.
But, now that C++20 have support for ranges I would like to build something similar.
The problem with filter and transform is that they iterate a range?
How would I go about doing this? I have been stuck with this for a while and would hate to use Expression Templates for the same.
Let's say for example I have two vectors M1{1,2,3} and M2{4,5,6}.
I would like to use the ranges library to overload a operator to return a view which contains matrix addition of these two - M1+M2 := {5,7,9}.
With ranges-v3, I can perform auto sum = zip_with(std::plus,M1,M2);
The above expression is evaluated lazily. How can I re-create this expression with C++20 Ranges?
The principle is quite trivial. Create an iterator that stores an iterator for each vectors, that when incremented, increments the two stored iterators and does the addition only when it is dereferenced.
Here is a piece of code that shoes the principle:
template <class It1, class It2>
struct adder_iterator{
It1 it1;
It2 it2;
decltype(auto)
operator++(){
++it1; ++it2;
return *this;
}
auto
operator *()const{
return *it1+*it2;
}
//....
};
You will also need to implement a sentinel and a view (by deriving from std::view_interface).
The sentinel is the end iterator. You can use the adder_iterator class for that. But you can think about optimization: in your view constructor, you ensure that the shortest vector begin iterator is always it1 end then only use this iterator to test the end of the iteration. You should try to see.
I don't know what is allowed in c++20, but the following works with range-v3's cpp20 namespace.
#include <range/v3/all.hpp>
#include <vector>
#include <iostream>
int main() {
std::vector<int> m1 = {1, 2, 3};
std::vector<int> m2 = {4, 5, 6};
auto sum = ranges::cpp20::views::transform(m1, m2, std::plus{});
for (auto i : sum)
std::cout << i << " "; // 5 7 9
}
While running an example that shows how to erase a range from std::map/multimap I have noticed strange behaviour in the following code:
#include <map>
#include <iostream>
#include <string>
int main()
{
std::multimap<int, std::string> myMap;
myMap.insert(std::make_pair(3, "three1"));
myMap.insert(std::make_pair(3, "three2"));
myMap.insert(std::make_pair(3, "three3"));
myMap.insert(std::make_pair(45, "fourty five"));
myMap.insert(std::make_pair(-1, "minus one"));
std::multimap<int, std::string>::iterator iter = myMap.find(3);
if (iter != myMap.end()) {
myMap.erase(iter, iter++); //segmentation fault(!)
}
for (auto element : myMap) {
std::cout << element.first << " -> " << element.second << std::endl;
}
return 0;
}
Which I build with command g++ --std=c++11 main.cpp (I use g++ 5.2.1).
Why post-incrementation of my iterator causes a Segmentation fault?
I would rather say that this should create 2 copies of this iterator, pass them into the erase method, "erase nothing" just as would code myMap.erase(iter, iter); and then increment the iter.
What logic stands behind this segfault?
Is this an invalid use of iter iterator? If so - why?
BTW.
It compiles when I use pre-incrementation myMap.erase(iter, ++iter) and here it "erase nothing" as I mentioned above.
The order of evaluation of the arguments to a function call is not defined. So when you write:
myMap.erase(iter, iter++); //segmentation fault(!)
the compiler is free to evaluate the second argument first, or not. As you use the same iterator, but have a side effect, you get Undefined Behaviour (refer to C++ standard, section 1.9/15).
For example, if the compiler evaluates first the second argument iter++, the incremented iterator would be used as the first argument, while second argument is not incremented iter. As a consequence: the range passed to erase() would be [std::next(iter), iter)- the function might attempt to erase elements that are out of range (i.e. UB).
As suggested by David in the comments, you can solve the issue with iter = myMap.erase(iter) (or using a range without side effects).