Home > Enterprise >  How did this loophole around const member function worked?
How did this loophole around const member function worked?

Time:02-02

In the below code we try to multiply each element's data in the list by 2 and assign it. But the apply function is a const function therefore should not be able to change the values of member fields. Output for the fifth line in main is

6
4
2
2
4

So code below succeeds in changing the values as intended and I can't figure out why.

#include <iostream>
#include <list>
#include <string>

using std::ostream;
using std::cout;
using std::endl;

template<class E> class MyList {
class Node {
    friend class MyList<E>;
    E data;
    Node* next = nullptr;
  }; // end of class Node
  Node* head = new Node;
  Node* tail = head;
  MyList(const MyList&) = default;
public:
  MyList() = default;
  MyList& operator=(const MyList&) = delete;
  void push_front(const E& data) {
    Node* node = new Node;
    node->data = data;
    node->next = head->next;
    head->next = node;
    if(head->next == nullptr) tail = node;
  }
  void push_back(const E& data) {
    if(head->next == nullptr) {
        push_front(data); return;
    }
    MyList temp(*this);
    temp.head = temp.head->next;
    temp.push_back(data);
    temp.head = nullptr;
  }
  ~MyList() {
    Node *node = head, *next;
    while(node != nullptr) {
        next = node->next;
        delete node;
        node = next;
    }
  }
  template<class Function>
  void apply (Function f) const {
    Node* node = head->next;
    while(node != nullptr) {
        f(node->data);
        node = node->next;
    }
  }
};


int main() {
    MyList<int> m1;
    m1.push_back(3);
    for(int i = 1; i <= 2;   i) m1.push_front(i); 
    for(int i = 1; i <= 2;   i) m1.push_back(i); 
    m1.apply(
        [](auto& val){ val *= 2;}
    );
    m1.apply(
        [](const auto& val){cout << val << endl;}
    );
    return 0;
}

CodePudding user response:

The key is logical vs bitwise constness. The head data member is a non-const pointer to non-const Node: the const correctness of the apply member function is bitwise constness:

  • you cannot change what the head data member (pointer) points to from a const-qualified member function.

You can, however, mutate the Node object that it points to.

CodePudding user response:

Because it is not the same, the pointer you store in your const struct as a member, than the data pointed by that pointer, which still is of type Node* (non const).

Just for the sake of showing, try to set all the pointers to NULL:

while(node != nullptr) {
    f(node);  // Pass the Node*, instead of the reference to the value
    node = node->next;
}

The above code will not compile, as you will be passing a Node* const& to your lambda, which will be a const reference and will not be possible to set it to NULL.

The const in the functions declarations is not transitive. The pointed memory accessed through some pointer member is not affected by that. It will still have the same type as that of the declared pointer member, in this case Node*. This is called (as stated in the other answer) logical constness, and is ensured by the function const signature. While you are trying to achieve bitwise constness, which is not achieved with the signature, but with the type declaration.

And about types, inside the const function the type of the pointer would become Node* const, meaning a constant pointer (logical constness, cannot change where it points), while the type of the data pointed is Node* (it can be changed, bitwise constness)

  •  Tags:  
  • Related