Your problem is that you have two different “semantics of meanings” before Node .
One of them displays operator< , which is not affected by add_neighbour . This is one set need to streamline things and which it provides by making Node const .
Another is that the class API is displayed, where both set_id and add_neighbour will change the value.
To preserve the sorted set , you should not change the node identifier after setting it. But you can let the neighbors change.
So, I suggest you make the neighbours set mutable , make add_neighbour private and const and make Graph a friend from Node .
This is what mutable gives you, data members that are not part of the "value" of the type. Note that this means that you indicate that something containing const Node* can expect the result of is_neighbour change between calls.
So...
class Node { private: // Trust Graph not to mess directly with these! int id; mutable std::set<Node*> neighbours; friend class Graph; // For Graph exclusive use void add_neighbour(Node* neighbour) const; public: Node(); Node(int id_p); void set_id(const int& id_p); // Callable when not in Graph set int get_id() const; void add_neighbour(Node* neighbour); // Callable when not in Graph set bool is_neighbour(Node* neighbour) const; friend bool operator <(const Node& lhs, const Node& rhs); }; class Graph { private: std::set<Node> node_list; public: Graph(); void add_node(int id); const Node* get_node_by_id(int id) const; bool has_node(int id) const; void check_add_node(int id); void add_edge(int id_1, int id_2); bool has_edge(int id_1, int id_2) const; void check_add_edge(int id_1, int id_2); (...) };
You now have public, non-constant mutators for Node instances that are not in the Graph set , and an additional mutator for Graph used to change Node neighbors to set .
So only Graph can perform
const Node b; b.add_neighbour(nullptr);
If you really don't trust Graph , you can replace the private const add_neighbour with an inner class using the static add_neighbour(Node* node, Node* neighbour method static add_neighbour(Node* node, Node* neighbour , since the inner class implicit for accessing the personal data of the outer class.
class NeighbourHelper { friend class Graph; static void add(const Node* node, Node* neighbour) { node->add_neighbour(neighbour); }
Now only Graph can execute
const Node b; Node::NeighbourHelper::add(&b, nullptr);
In both cases, the following works for everyone:
Node a; a.add_neighbour(nullptr);
At this point, you should suffer from the smell of code ... The problem is the public get_node_by_id method in Graph. You will likely want to set some kind of iterator instead, not raw Node* and make Node private inner class of Graph.
Or just replace the whole Node concept with std::map<int,std::set<int>> ...
But it depends on your actual use.