c++autorange-based-loop

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


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:

  1. Error (active) E1086 the object has type qualifiers that are not compatible with the member function "Term::delMinterm"
  2. 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.


Solution

  • 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());