How do I use std:vector's erase() function properly? - c++

I am having a strange issue when using the erase() function on a std:vector. I use the following code:
int count = 0;
for (int itr=0; itr<b.size(); ++itr) {
if (b[count].notEmpty = false) {
b.erase(b.begin()+count);
--count;
}
++count;
}
However, for some reason there are no elements actually getting erased from b. b is declared elsewhere as follows:
vector<block_data> b;
Where block_data is a structure, and contains the boolean value notEmpty. Some of b's elements are properly being assigned with notEmpty = false earlier in the code, so I am not sure why they aren't getting erased. Is it an error with the syntax, or something else?

There's nothing wrong with your use of erase. The problem is an assignment inside the if condition:
if(b[count].notEmpty = false)
This sets b[count].notEmpty to false, then returns false. This will cause the inner-body of the if-statement to never run.
Change it to
if(b[count].notEmpty == false)
or event
if(!b[count].notEmpty)
And you should be good to go.

Others have pointed out how to fix your code, but just in case: how to use a Standard algorithm.
// Decide if an element is to be removed
auto predicate = [](block_data& b)
{
// more idiomatic than b.notEmpty == false
return !b.notEmpty;
});
// Remove
auto removed = std::remove_if(b.begin(), b.end(), predicate);
// Count
auto count = b.end() - removed;
// Erase.
b.erase(removed, b.end());

b[count].notEmpty = false should be b[count].notEmpty == false, otherwise the if will always be false.
Better practice to write false == b[count].notEmpty, in this way the constant on the left is not an l-value, and if you make the (quite common) mistake of writing = instead of == you'll get a compilation error.

Related

Turning while + if else to simpler std::remove_if

I am trying to clean up some loops, and move from while and if/else into std::remove_if and have never done this before, can anyone show me how to turn the below into a remove_if that will achieve the same results? Ideally I want to eliminate the if(obj->IsQueuedForRemoval).
Thank you in advance!
Initial:
void ObjectCollection::ProcessRemovals()
{
bool removed = false;
auto objIterator = objects.begin();
while (objIterator != objects.end())
{
auto obj = *objIterator;
if (obj->IsQueuedForRemoval())
{
objIterator = objects.erase(objIterator);
removed = true;
}
else
{
++objIterator;
}
}
if (removed)
{
drawables.ProcessRemovals();
collidables.ProcessRemovals();
}
}
The part I'm getting tripped up on is how I can keep the bool, trigger it if it removes anything and only go to drawables and collidables process removals if it was removed from there.
std::remove_if returns an iterator to end of the range of not removed elements. You can split the erase-remove into two steps:
auto it = std::remove_if(objects.begin(), objects.end(),predicate)
bool removed = (it != objects.end());
if (removed) {
objects.erase(it,objects.end());
drawables.ProcessRemovals();
collidables.ProcessRemovals();
}
Where predicate can be a lambda that checks if the object is queued for removal (assuming IsQueuedForRemoval is const):
auto predicate = [](const Object& obj){ return obj.IsQueuedForRemoval();};
I'll leave it to you to adjust the lambda in case the containers elements type is something else than Object.

How to fix "not all control paths return a value" in lambda expression

I have some code with lambda expression and my problem is that I would like to return something only if "if" isn't nullptr. Other way I don't want to return anything. Is there a return that can be used in this code? Or maybe another way to not getting warning?
auto iter = std::stable_partition(object1->vector_.begin(), object1->vector_.end(), [](Class* x)
{
if (x->object2_ != nullptr)
{
return !x->object2->parameter_;
}
});
It's working good, but this warning is annoying and I know that I should do something with this.
The intent of std::stable_partition is:
Reorders the elements in the range [first, last) in such a way that all elements for which the predicate p returns true precede the elements for which predicate p returns false. Relative order of the elements is preserved.
When you use
return !x->object2->parameter_;
you want to put all the elements for which !x->object2->parameter_ is true to the left and all the elements for which !x->object2->parameter_ is false to the right.
Judging by that, I would say if x->object2_ is a nullptr, you should put them to the right. Hence, the default return needs to be
return false;
Hence,
auto iter = std::stable_partition(object1->vector_.begin(),
object1->vector_.end(),
[](Class* x) -> bool
{
if (x->object2_ != nullptr)
{
return !x->object2->parameter_;
}
return false;
});
You can combine the body of the function to one line as:
auto iter = std::stable_partition(object1->vector_.begin(),
object1->vector_.end(),
[](Class* x) -> bool
{
return (x->object2_ != nullptr) && (!x->object2->parameter_);
});

Map/iterator incremental error

The following code throwing debug assertion map/iterator incremental error ..
void ClassA::Remove()
{
std::map<int, CVClassB*>::iterator it(m_p.begin());
while ( it != m_p.end() )
{
if (it->first >= 0)
{
m_p.erase(it);
it++;
}
}
}
Can you please let me know what is the error
std::map::erase invalidates the iterator on which it operates. So it is not safe to increment it afterwards. But erase() does return the next iterator for you:
it = m_p.erase(it);
Also, you only increment it inside the if, so unless all the keys are >=0, you will get stuck in an infinite loop. You probably wanted something like:
// delete all keys >= 0
if (it->first>=0) {
it = m_p.erase(it); // erase and increment
}
else {
++it; // just increment
}
Also, as Vlad's answer alludes to, who manages the lifetime of the CVClassB*? Do you need to delete it? Why use a pointer at all, you can probably store the value in the map directly. (Or use a smart pointer).
Write the loop like
while ( it != m_p.end() )
{
if (it->first >= 0)
{
it = m_p.erase(it);
}
else
{
++it;
}
}
Also it seems you should delete the object pointed to by the erased iterator.
For example
delete *it;
it = m_p.erase(it);
Your invalidating the iterator by removing inside the loop but in any case all that does is clear the map. Just call m_p.clear() and it will do exactly what you are trying to do. Although not sure what your trying to do is what you intended to do but that's another issue.
If you want to delete the objects pointed to then delete them then clear the map.
for(item : m_p)
delete item->second;
m_p.clear();
//done

Vector iterators incompatible... but why?

I receive the message "Vector iterators incompatible". I tried to wrap my head around it, but nothing. I did it before. Same code, just not used in a class that receives "cWORLD* World". What am I doing wrong?
Thank you!
else if (Click[2] == true)
{
//go through objects and check collision
for (vector<cOBJECT*>::iterator it = World->ReturnWorldObjects().begin(); it != World->ReturnWorldObjects().end();)
{
//Check for collision and delete object
if (PointInRect(MouseX + offX, MouseY + offY, (*it)->getrect()) == true)
{
// delete object, delete slot, pick up next slot
delete *it;
it = World->ReturnWorldObjects().erase(it);
}
else
{ // no action, move to next
++it;
}
}//for
}//else if (Click[2] == true)
Looks like ReturnWorldObjects returns copy of vector, not reference. In this case, you are trying to compare iterators of different objects, that is not checked by standard, but can be checked by checked iterators (in this case, I think it's MSVC checked iterators).
Like #ForEveR already mentioned, you possibly return a copy of a vector in the function ReturnWorldObjects(). Without seeing the declaration of this method I can only assume it's something like vector<cOBJECT*> ReturnWorldObject();
You can come around this with 2 Solutions, I think:
1. Return a reference to the vector in your World Class
const vector<cOBJECT*>& ReturnWorldObjects()
{
return m_vecWorldObjects; // Your vector here
}
2. Get one copy of that function and use that in your code
...
vector<cOBJECT*> worldObjects = World->ReturnWorldObjects();
for (vector<cOBJECT*>::iterator it = worldObjects.begin(); it != worldObjects.end(); it++)
{
...
}
...

C++ boolean logic error possibly caused by if statements

Here is an extremely simplified version of a section of code that I am having trouble with.
int i = 0;
int count = 0;
int time = 50;
int steps = 1000;
double Tol = 0.1;
bool crossRes = false;
bool doNext = true;
for (int i=0; i<steps; i++) {
//a lot of operations are done here, I will leave out the details, the only
//important things are that "dif" is calculated each time and doNext either
//stays true or is switched to false
if (doNext = true) {
if (dif <= Tol) count++;
if (count >= time) {
i = steps+1;
crossRes = true;
}
}
}
if (crossRes = true) {
printf("Nothing in this loop should happen if dif is always > Tol
because count should never increment in that case, right?");
}
My issue is that every time it gets done with the for loop, it executes the statements inside the "if (crossRes = true)" brackets even if count is never incremented.
You've made a common (and quite frustrating) mistake:
if (crossRes = true) {
This line assigns crossRes to true and returns true. You're looking to compare crossRes with true, which means you need another equals sign:
if (crossRes == true) {
Or more concisely:
if (crossRes) {
I stand corrected:
if (crossRes)
You wouldn't have this problem if your condition was
if (true = crossRes)
because it wouldn't compile.
`crossRes = true` always evaluates to `true` because it's an assignment, to `true`.
You want `crossRes == true`:
if (crossRes == true) {
printf("Nothing in this loop should happen if dif is always > Tol
because count should never increment in that case, right?");
}
= is assignment, == is equality comparison. You want:
if (crossRes == true) {
You make the same mistake here:
if (doNext = true) { // Bad code
The other answers here have told you the problem. Often your compiler will warn you but a way to ensure that you do not do this is to put the constant term on the left
true == crossRes
that way you get a compiler error instead of a warning and so it can't escape unnoticed since
true = crossRes
wont compile.
First, although a number of people have pointed to the problem with if (crossRes = true), for some reason they haven't (yet, anyway) pointed to the same problem with if (doNext = true).
I'll stick to pointing out that you really want if (crossRes) rather than if (crossRes == true) (or even if (true == crossRes)).
The first reason is that it avoids running into the same problem from a simple typo.
The second is that the result of the comparison is a bool -- so if if (crossRes==true) is necessary, you probably need if (((((crossRes == true) == true) == true) == true) just to be sure (maybe a few more -- you never know). This would, of course, be utterly silly -- you're starting with a bool, so you don't need a comparison to get a bool.
I'd also note for the record, that if you insist on doing a comparison at all, you should almost always use if (x != false) rather than if (x == true). Though it doesn't really apply in C++, in old C that doesn't have an actual Boolean type, any integer type can be used -- but in this case, a comparison to true can give incorrect results. At least normally, false will be 0 and true will be 1 -- but when tested, any non-zero value will count as equivalent to true. For example:
int x = 10;
if (x) // taken
if (x == true) // not taken, but should be.
If you're not starting with a Boolean value as you are here, then the if (<constant> <comparison> <variable>) makes sense and is (IMO) preferred. But when you're starting with a Boolean value anyway, just use it; don't do a comparison to produce another of the same.