C++: STL: set: stored value constness

1.2k views Asked by At

Having the following code:

#include <iostream>
#include <set>
#include <string>
#include <functional>

using namespace std;

class Employee {
  // ...
  int _id;
  string _name;
  string _title;
public:
  Employee(int id): _id(id) {}

  string const &name() const { return _name; }
  void setName(string const &newName) { _name = newName; }

  string const &title() const { return _title; }
  void setTitle(string const &newTitle) { _title = newTitle; }

  int id() const { return _id; }
};

struct compEmployeesByID: public binary_function<Employee, Employee, bool> {
  bool operator()(Employee const &lhs, Employee const &rhs) {
    return lhs.id() < rhs.id();
  }
};

int wmain() {
  Employee emplArr[] = {0, 1, 2, 3, 4};
  set<Employee, compEmployeesByID> employees(emplArr, emplArr + sizeof emplArr/sizeof emplArr[0]);
  // ...
  set<Employee, compEmployeesByID>::iterator iter = employees.find(2);
  if (iter != employees.end())
    iter->setTitle("Supervisor");

  return 0;
}

I cannot compile this code having (MSVCPP 11.0):

1>  main.cpp
1>d:\docs\programming\test01\test01\main.cpp(40): error C2662: 'Employee::setTitle' : cannot convert 'this' pointer from 'const Employee' to 'Employee &'
1>          Conversion loses qualifiers

This helps to compile:

  if (iter != employees.end())
    const_cast<Employee &>(*iter).setTitle("Supervisor");

The question: I know that map and multimap store their values as pair(const K, V) where K is a key and V is a value. We cannot change the K object. But set<T> and multiset<T> store their object as T, not as const T. So WHY I NEED THIS CONST CAST??

4

There are 4 answers

10
Alan Stokes On BEST ANSWER

In C++11 set (and multiset) specify that iterator as well as const_iterator is a constant iterator, i.e. you cannot use it to modify the key. This is because any modification of they key risks breaking the set's invariant. (See 23.2.4/6.)

Your const_cast opens the door to undefined behaviour.

0
Aaron McDaid On

The values in a set are not supposed to be modified. For example, if you modified your Employee's ID, then it would be in the wrong position in the set and the set would be broken.

Your Employee has three fields, and your set is using the _id field in your operator<.

class Employee {
  // ...
  int _id;
  string _name;
  string _title;

};

Therefore, you should probably use a map<int,Employee> instead of your set, then you would be able to modify the name and title. I would also make the _id field of Employee a const int _id.

(By the way, identifiers beginning with _ are technically reserved and should be avoided. It's never cause me any trouble but now I prefer to put the underscore on the end of the variable name.)

0
John Humphreys On

In C++, you cannot modify keys of associated STL containers because you will break their ordering. When you wish to change a key, you're supposed to (1) find the existing key, (2) delete it, and (3) insert the new key.

Unfortunately, while this isn't overly appealing, it's how associative containers work in the STL.

0
curiousguy On

You can get away with const with just an indirection.

But be careful to not change the ordering of the elements in a given sorted container.