c++ compiler choosing selectively when it throws errors

The code below is just a binary tree that inserts Nodes into it.

When I run the code below, it throws the error (in insert method of Tree) of

"Line 135: Char 35: error: assigning to ‘Node *’ from
‘const Node *’ discards qualifiers on.left = &add"

, I’m told this occurred because I’m trying to assign a constant to a non constant, which is supposedly never allowed.

But for some reason, in the constructor for Tree, I also assign the constant reference to a non constant member variable Root using member initialization list, but here the error is not thrown.

I’m confused by this inconsistency. My textbook & cpp reference page on constant qualifier doesn’t address why the member initialization list allows me to assign constants to non constants. Why am I allowed to assign constant variable to non constant variables, but other times can’t? In which situations am I allowed to assign constants to non constants?

#include <sstream>
#include <string>
#include <vector>
#include <iostream>

using namespace std;

template <typename T>
struct Node {
    T val;
    Node* left;
    Node* right;

    Node (const T &val) : val(val), left(nullptr), right(nullptr) {

    }

};


template <typename T>
class Tree {
    public:
        Node<T> root;
        Tree(const Node<T>& r) : root(r) { //why no error here? I'm also discarding qualifier!
            std::cout<< "why no error?"<<std::endl;
        }



        void insert(const Node<T> &add) {
            Node<T>& on = root;
            
            while (true) {
                if (add.val < on.val) {
                    if (on.left == nullptr) {
                        on.left = &add; //why error here????!!!!
                        return ;
                    } else {
                        on=on->left;
                    }
                } else {
                    if (on.right == nullptr) {
                        on.right=&add;
                        return ;
                    } else {
                        on=on->right;
                    }
                }
            }
            
            return ;
        }


}; 




int main() {
    using namespace std;

    Tree<int> mytree(Node<int>(15));
    
    mytree.insert(Node<int>(2));
    
    cout<<"code finished without undefined behavior"<<endl;

    return 0;
}

>Solution :

The rule about not assigning const to non-const exists because doing so would allow you to modify const objects. E.g.

const int one = 1;
const int* ptr = &one;
int* bad = ptr;   // not really allowed
*bad = 2;         // changes the const object `one`

But the other situation you describe is different

template <typename T>
class Tree {
public:
    Node<T> root;
    Tree(const Node<T>& r) : root(r) {}
    ...
};

Here r is a const reference to an object which is being used to initialise a completely different object called root. If you subsequently change root it has no effect at all on the object referred to by r. So there is no problem with const here.

But one small change and there is an error

template <typename T>
class Tree {
public:
    Node<T>& root; // a reference
    Tree(const Node<T>& r) : root(r) {}
    ...
};

Now root is a reference, not a separate object, so the const to non-const rule applies.

In general you need to understand the difference between copying objects and copying references or pointers to objects. When you copy an object there are now two separate independent objects, when you copy a pointer or a reference there is only one actual object.

Leave a Reply