Can't modify the value of a reference in Range based loop - c++

I'm working on a school project of boolean minimization, and here I want to delete some elements of a set of my user defined class.
This is where the error occurs:
(dc and PI are both sets of my class Term, passed to this function by reference. std::set<Term>& dc, PI)
for (const auto& n : dc) {
for (const auto& i : n.getMinterm()) {
m[i] = 0;
for (auto &x : PI) {
x.delMinterm(i);
}
}
}
The error message is:
Error (active) E1086 the object has type qualifiers that are not compatible with the member function "Term::delMinterm"
Error C2662 'void Term::delMinterm(int)': cannot convert 'this' pointer from 'const Term' to 'Term &'
This is the content of my class:
class Term {
private:
int group = 0;
int literal = 0;
std::string term;
std::set<int>minterm;
bool isDontCare;
bool merged;
};
Function delMintern(int) just erases the selected element from the set minterm.
Though I didn't use "const auto&" but "auto&", it still shown as a const object.
I've tried taking off the '&' but it just create a local duplicate, however, I want to directly modify the original one.
I also tried something like:
for (auto x : PI) {
PI.erase(x);
x.delMinterm(i);
PI.insert(x);
}
but it caused a "read access violation" error.

You can't modify a reference to x because it is const. It is const because iterating a std::set through loop gives only const values.
See solution with const_cast example code at the end of my answer.
It is known that std::set stores all entries in a sorted tree.
Now imagine if you can modify a variable when iterating a loop, it means that after modification sorted order of std::set might be changed. But std::set should always keep invariant of its tree, hence it can't allow to make any modifications thus gives only const values when iterating.
If you need to really modify set entry then you have to take it from set, delete from set and that add again to set. Even if sorted order is not changed after your modification, still you need to reinsert into set.
But there is a hacky workaround - you can make your method delMinentry as having const modifier. Then all fields that it modifies should be marked as mutable. Mutable fields allow modifications from const methods. And std::set allows to call const methods when iterating.
There is one more workaround - you make delMinterm() as const method, but inside this method do const_cast<Term &>(*this).delMintermNonConst(), in other words from const method you can call non-const method if you do const_cast. Also you can do const cast directly on loop variable if you're sure what you do, in other words if you modify Term in such a way that std::set sorted order is not changed:
for (auto &x : PI) {
const_cast<Term &>(x).delMinterm(i);
}
If delMinterm() results in such modification of a Term after which order of std::set may change then you can't do const_cast in code above. In other words if after delMinterm your operator < may give a different result between terms, then you can't do this const cast and you have to reinsert into set (delete Term and add again).
Also don't forget that after reinserting into set you have to redo set iteration loop again from start, because after change to inner structure you can't keep iterating loop running further, iterators are invalidated.
If set's order changes (hence you can't do const_cast) then you have to re-insert values of set, do this by copying values to vector, modifying them through delMinterm(), copying back to set, like this:
std::vector<Term> scopy(PI.cbegin(), PI.cend());
for (auto & x: scopy)
x.delMinterm(i);
PI = std::set<Term>(scopy.cbegin(), scopy.cend());

Related

Binary '=': no operator found which takes a left-hand operand of type 'const Thing'

I have read duplicates but really didn't help me.
I am trying to reach next behaviour.
Having a vector composed by pairs {Thing, set < Thing >}
I want a final result of{Thing, newSetOfThing < Thing >}
where that 'newSetOfThing' is the difference applied with every other
sets in the vector but himself. Difference means to have all values but contained on other sets. I am using std::set_difference.
Giving a closer example with numbers.
vector = {[1, {3,4,5,7}], [2,{1,3,9}], [3, {1,2,12}]};
==>
vectorResult = {[1, {4,5,7}], [2, {9}], [3, {2,12} }
Then my code looks like this:
class Thing {
public:
Thing () {
};
~Thing () {};
int position; //id
bool operator<(const Thing &Other) const;
};
bool Thing::operator<(const Thing &Thing) const
{
return(Other.position<position);
}
//The original vector
vector<pair<Thing, set<Thing>>> pairWithSet;
// I fill vector here [...]
// variable I define to store partial results
set<Thing> differenceResult;
// Vector I want to fill for results
vector<pair<Thing, set<Thing>>> newPairWithSet;
// Operation
for (pair<Thing, set<Thing>> currentPair : pairWithSet){
Thing currentThing= currentPair.first;
differenceResult = currentPair.second;
for (int i=0; i<pairWithSet.size();i++) {
if (pairWithSet[i].first.position != currentThing.position) {
set_difference(differenceResult.begin(),
differenceResult.end(),
pairWithSet[i].second.begin(),
pairWithSet[i].second.end(),
differenceResult.begin());
}
newPairWithSet.push_back(pair<Thing, set<Thing>>(currentThing, differenceResult));
}
I explain my objective to you have a point from where to go but at the end I think problem is more related of how wrong I am using set_difference operation and that I cant directly assign 'Thing'. So set_difference has not a way to check if they are the same. Because error is
binary '=': no operator found which takes a left-hand operand of type
'const Thing' (or there is no acceptable conversion
I say because maybe there are other errors to reach behaviour since I can't still debug until I solve problem with operator.
My question is if I need to declare that '=' operation and how. Or If I miss something and I need to perform by another way.
I can ensure problem is when I use set_difference, If I comment this part compiler does the task:
set_difference(differenceResult.begin(),
differenceResult.end(),
pairWithSet[i].second.begin(),
pairWithSet[i].second.end(),
differenceResult.begin());
And I think is because at the end it is trying to perform an assignment to a const (because std::pair declaration?), as it says error (then obviously compiler does not know how to operate). So I have not clear how to perform it recursive set_difference.
set_difference writes results into place pointed by its 5-th argument. You are passing differenceResult.begin() what is wrong, because begin for set always returns const iterator. You cannot do write operation where a destination is object pointed by const iterator.
If you want to store Thing objects into set as result of set_difference algorithm, you can use std::inserter:
set<Thing> res; // CREATE EMPTY SET
set_difference(differenceResult.begin(),
differenceResult.end(),
pairWithSet[i].second.begin(),
pairWithSet[i].second.end(),
std::inserter(res,res.begin())); // CREATE INSERT_ITERATOR WHICH INSERTS THINGS INTO RES
then you can copy res into newPairWithSet.

insert doesn't permanently change map?

I have a member variable of this class that is set<pair<string,map<string,int> > > setLabelsWords; which is a little convoluted but bear with me. In a member function of the same class, I have the following code:
pair<map<string,int>::iterator,bool> ret;
for (auto j:setLabelsWords) {
if (j.first == label) {
for (auto k:words) {
ret = j.second.insert(make_pair(k,1));
if (ret.second == false) {
j.second[k]++;
}
}
}
}
"words" is a set of strings and "label" is a string. So basically it's supposed to insert "k" (a string) and if k is already in the map, it increments the int by 1. The problem is it works until the outermost for loop is over. If I print j.second's size right before the last bracket, it will give me a size I expect like 13, but right outside the last bracket the size goes back to 7, which is what the size of the map is initially before this code block runs. I am super confused why this is happening and any help would be much appreciated.
for (auto j:setLabelsWords) {
This iterates over the container by value. This is the same thing as if you did:
class Whatever { /* something in here */ };
void changeWhatever(Whatever w);
// ...
{
Whatever w;
changewhatever(w);
}
Whatever changewhatever does to w, whatever modifications are made, are made to a copy of w, because it gets passed by value, to the function.
In order to correctly update your container, you must iterate by reference:
for (auto &j:setLabelsWords) {
for (auto j:setLabelsWords) {
This creates a copy of each element. All the operations you perform on j affect that copy, not the original element in setLabelsWords.
Normally, you would just use a reference:
for (auto&& j:setLabelsWords) {
However, due to the nature of a std::set, this won't get you far, because a std::set's elements cannot be modified freely via iterators or references to elements, because that would allow you to create a set with duplicates in it. The compiler will not allow that.
Here's a pragmatic solution: Use a std::vector instead of a std::set:
std::vector<std::pair<std::string, std::map<std::string,int>>> setLabelsWords;
You will then be able to use the reference approach explained above.
If you need std::set's uniqueness and sorting capabilities later on, you can either apply std::sort and/or std::unique on the std::vector, or create a new std::set from the std::vector's elements.

RunTime Error : map/set iterators incompatible

I have a runtime error "map/set iterators incompatible" at line 8.
void Manager::Simulate(Military* military, Shalishut* shalishut,char* args[]){
Simulation* simulation = Simulation::GetInstance();
Time* time = Time::GetInstance();
multimap<int,Task*>::iterator itTasks;
itTasks = simulation->GetTasks().begin();
while(itTasks != simulation->GetTasks().end()){
while (itTasks->second->GetTimeStamp() == time->GetTime()){ /*line 8 - ERROR*/
TaskExecute(itTasks->second,military,shalishut,args);
itTasks++;
}
// Unit take car of vehicles
time->TimeIncrease();
}
}
Simulation is declared as a multimap<int,Task*>. What is the problem?
I'm going to take a wild guess and say that the Simulation::GetTasks() signature looks like this:
multimap<int,Task*> GetTasks() const;
This creates a new multimap (a copy) each time you call it.
When comparing iterators, both of the multimap<int,Task*> iterators must come from the same container; since you're getting a new copy each time you call GetTasks(), you violate this constraint, and this is the source of your error. You also have another problem - the temporary multimap copies are destroyed after the statement they're created in, so your iterators are invalidated instantly.
You have two choices; one is to capture a copy locally and use that copy consistently:
multimap<int,Task*> tasks = simulation->GetTasks();
multimap<int,Task*>::iterator itTasks;
itTasks = tasks.begin();
while(itTasks != tasks.end()){
while (itTasks->second->GetTimeStamp() == time->GetTime()){
TaskExecute(itTasks->second,military,shalishut,args);
itTasks++;
}
// Unit take car of vehicles
time->TimeIncrease();
}
Another is to have GetTasks() return a reference to a persistent multimap, ensuring the same one is used each time:
multimap<int,Task*> &GetTasks();
Or a const reference:
const multimap<int,Task*> &GetTasks() const;
This has the advantage of avoiding the (potentially large) overhead of copying the multimap.
Note that using a const reference requires using const_iterators to step through the multimap. I would recommend defining both const and non-const accessors (C++ will pick the right one based on if the Simulation pointer or reference is const), unless you want to disallow direct modification of the underlying multimap entirely, in which case you can define only the const variant.

How to achieve better efficiency re-inserting into sets in C++

I need to modify an object that has already been inserted into a set. This isn't trivial because the iterator in the pair returned from an insertion of a single object is a const iterator and does not allow modifications. So, my plan was that if an insert failed I could copy that object into a temporary variable, erase it from the set, modify it locally and then insert my modified version.
insertResult = mySet.insert(newPep);
if( insertResult.second == false )
modifySet(insertResult.first, newPep);
void modifySet(set<Peptide>::iterator someIter, Peptide::Peptide newPep) {
Peptide tempPep = (*someIter);
someSet.erase(someIter);
// Modify tempPep - this does not modify the key
someSet.insert(tempPep);
}
This works, but I want to make my insert more efficient. I tried making another iterator and setting it equal to someIter in modifySet. Then after deleting someIter I would still have an iterator to that location in the set and I could use that as the insertion location.
void modifySet(set<Peptide>::iterator someIter, Peptide::Peptide newPep) {
Peptide tempPep = (*someIter);
anotherIter = someIter;
someSet.erase(someIter);
// Modify tempPep - this does not modify the key
someSet.insert(anotherIter, tempPep);
}
However, this results in a seg fault. I am hoping that someone can tell me why this insertion fails or suggest another way to modify an object that has already been inserted into a set.
The full source code can be viewed at github.
I agree with Peter that a map is probably a better model of what you are doing, specifically something like map<pep_key, Peptide::Peptide>, would let you do something like:
insertResult = myMap.insert(std::make_pair(newPep.keyField(), newPep));
if( insertResult.second == false )
insertResult.first->second = newPep;
To answer your question, the insert segfaults because erase invalidates an iterator, so inserting with it (or a copy of it) is analogous to dereferencing an invalid pointer. The only way I see to do what you want is with a const_cast
insertResult = mySet.insert(newPep);
if( insertResult.second == false )
const_cast<Peptide::Peptide&>(*(insertResult.first)) = newPep;
the const_cast approach looks like it will work for what you are doing, but is generally a bad idea.
I hope it isn't bad form to answer my own question, but I would like it to be here in case someone else ever has this problem. The answer of why my attempt seg faulted was given my academicRobot, but here is the solution to make this work with a set. While I do appreciate the other answers and plan to learn about maps, this question was about efficiently re-inserting into a set.
void modifySet(set<Peptide>::iterator someIter, Peptide::Peptide newPep) {
if( someIter == someSet.begin() ) {
Peptide tempPep = (*someIter);
someSet.erase(someIter);
// Modify tempPep - this does not modify the key
someSet.insert(tempPep);
}
else {
Peptide tempPep = (*someIter);
anotherIter = someIter;
--anotherIter;
someSet.erase(someIter);
// Modify tempPep - this does not modify the key
someSet.insert(anotherIter, tempPep);
}
}
In my program this change dropped my run time by about 15%, from 32 seconds down to 27 seconds. My larger data set is currently running and I have my fingers crossed that the 15% improvement scales.
std::set::insert returns a pair<iterator, bool> as far as I know. In any case, directly modifying an element in any sort of set is risky. What if your modification causes the item to compare equal to another existing item? What if it changes the item's position in the total order of items in the set? Depending on the implementation, this will cause undefined behaviour.
If the item's key remains the same and only its properties change, then I think what you really want is a map or an unordered_map instead of a set.
As you realized set are a bit messy to deal with because you have no way to indicate which part of the object should be considered for the key and which part you can modify safely.
The usual answer is to use a map or an unordered_map (if you have access to C++0x) and cut your object in two halves: the key and the satellite data.
Beware of the typical answer: std::map<key_type, Peptide>, while it seems easy it means you need to guarantee that the key part of the Peptide object always match the key it's associated with, the compiler won't help.
So you have 2 alternatives:
Cut Peptide in two: Peptide::Key and Peptide::Data, then you can use the map safely.
Don't provide any method to alter the part of Peptide which defines the key, then you can use the typical answer.
Finally, note that there are two ways to insert in a map-like object.
insert: insert but fails if the value already exists
operator[]: insert or update (which requires creating an empty object)
So, a solution would be:
class Peptide
{
public:
Peptide(int const id): mId(id) {}
int GetId() const;
void setWeight(float w);
void setLength(float l);
private:
int const mId;
float mWeight;
float mLength;
};
typedef std::unordered_map<int, Peptide> peptide_map;
Note that in case of update, it means creating a new object (default constructor) and then assigning to it. This is not possible here, because assignment means potentially changing the key part of the object.
std::map will make your life a lot easier and I wouldn't be surprised if it outperforms std::set for this particular case. The storage of the key might seem redundant but can be trivially cheap (ex: pointer to immutable data in Peptide with your own comparison predicate to compare the pointee correctly). With that you don't have to fuss about with the constness of the value associated with a key.
If you can change Peptide's implementation, you can avoid redundancy completely by making Peptide into two separate classes: one for the key part and one for the value associated with the key.

std::map iteration - order differences between Debug and Release builds

Here's a common code pattern I have to work with:
class foo {
public:
void InitMap();
void InvokeMethodsInMap();
static void abcMethod();
static void defMethod();
private:
typedef std::map<const char*, pMethod> TMyMap;
TMyMap m_MyMap;
}
void
foo::InitMap()
{
m_MyMap["abc"] = &foo::abcMethod;
m_MyMap["def"] = &foo::defMethod;
}
void
foo::InvokeMethodsInMap()
{
for (TMyMap::const_iterator it = m_MyMap.begin();
it != m_MyMap.end(); it++)
{
(*it->second)(it->first);
}
}
However, I have found that the order that the map is processed in (within the for loop) can differ based upon whether the build configuration is Release or Debug. It seems that the compiler optimisation that occurs in Release builds affects this order.
I thought that by using begin() in the loop above, and incrementing the iterator after each method call, it would process the map in order of initialisation. However, I also remember reading that a map is implemented as a hash table, and order cannot be guaranteed.
This is particularly annoying, as most of the unit tests are run on a Debug build, and often strange order dependency bugs aren't found until the external QA team start testing (because they use a Release build).
Can anyone explain this strange behaviour?
Don't use const char* as the key for maps. That means the map is ordered by the addresses of the strings, not the contents of the strings. Use a std::string as the key type, instead.
std::map is not a hash table, it's usually implemented as a red-black tree, and elements are guaranteed to be ordered by some criteria (by default, < comparison between keys).
The definition of map is:
map<Key, Data, Compare, Alloc>
Where the last two template parameters default too:
Compare: less<Key>
Alloc: allocator<value_type>
When inserting new values into a map. The new value (valueToInsert) is compared against the old values in order (N.B. This is not sequential search, the standard guarantees a max insert complexity of O(log(N)) ) until Compare(value,ValueToInsert) returns true. Because you are using 'const char*' as the key. The Compare Object is using less<const char*> this class just does a < on the two values. So in effect you are comparing the pointer values (not the string) therefore the order is random (as you don't know where the compiler will put strings.
There are two possible solutions:
Change the type of the key so that it compares the string values.
Define another Compare Type that does what you need.
Personally I (like Chris) would just use a std::string because < operator used on strings returns a comparison based on the string content. But for arguments sake we can just define a Compare type.
struct StringLess
{
bool operator()(const char* const& left,const char* const& right) const
{
return strcmp(left,right) < 0;
}
};
///
typedef std::map<const char*, int,StringLess> TMyMap;
If you want to use const char * as the key for your map, also set a key comparison function that uses strcmp (or similar) to compare the keys. That way your map will be ordered by the string's contents, rather than the string's pointer value (i.e. location in memory).