4

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??

curiousguy
  • 7,344
  • 2
  • 37
  • 52
nickolay
  • 2,860
  • 2
  • 25
  • 35
  • 4
    Actually, I think that `set`s *do* store their values such that they are not easily modifiable (effectively as `const`). If you modify a value, it's possible that the item is in the wrong place in the set, so it wouldn't make sense to allow the item to be modified. – Aaron McDaid Jan 16 '12 at 22:26
  • 2
    `std::unary_function` has been deprecated in 2011, you might want to replace the functor with a lambda anyway. – pmr Jan 16 '12 at 22:34
  • This is a warning that you're using `set` in the wrong way. Your records have keys and values, but you're storing them in a `set` instead of a `map`. – Omnifarious Jan 16 '12 at 22:49
  • 1
    @Omnifarious Thanks, I know. But I need this (probably badly designed) example for academic purposes. – nickolay Jan 16 '12 at 22:51
  • @Omnifarious "_This is a warning that you're using set in the wrong way._" There are one than one way to use a `set`. "_Your records have keys and values, but you're storing them in a set instead of a map._" `map` interface is almost always not the right one. You can use `set` with modifiable elements. – curiousguy Jul 21 '12 at 02:06
  • @curiousguy - Ahh, yes, because nobody ever wants an interface where they look up a value given a key. No, they always want to check for whether or not a given value is part of a set of larger values. Clear, the interface for map is all wrong and so useless that nobody should ever use it. Even if you do have a value associated with a key, really, it's just a key that's part of a set of other keys and that value is totally incidental and doesn't at all indicate that you shouldn't be creating some kind of weird fake prototype key/value combo to look up in your set. Never! – Omnifarious Jul 21 '12 at 19:07
  • @Omnifarious "_nobody ever wants an interface where they look up a value given a key._" No. But the key is almost always *inside* the value, not outside. – curiousguy Jul 21 '12 at 19:20
  • @curiousguy - Yes. But it's also true that any given value frequently contains multiple things you'd like to use as a key at different points in time. Also, when you're looking up the value, you usually only have the key, not the other parts of the value. So while the key is inside the value, it's really a separate entity. Using a set just because you have a key inside the value you want to point at is solving a resource management issue by using the wrong interface. – Omnifarious Jul 21 '12 at 19:29
  • @Omnifarious "_But it's also true that any given value frequently contains multiple things you'd like to use as a key at different points in time._" True. "_So while the key is inside the value, it's really a separate entity._" Exactly. "_Using a set just because you have a key inside the value you want to point at is solving a resource management issue by using the wrong interface._" Yes, all associative containers have the wrong interface. (You can work around the problem with fake "key-only" values, but it is really ugly.) – curiousguy Jul 21 '12 at 19:37

4 Answers4

12

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.

Alan Stokes
  • 18,320
  • 3
  • 41
  • 63
  • great answer. should be very helpful – nickolay Jan 16 '12 at 22:48
  • I've ISO/IEC 14882:2011(E) standard BUT 23.2.4/6 are not pointing out the `iterator` type. Instead you can see here **_X::iterator - iterator type whose value type is T** (23.2.4) – nickolay Jan 16 '12 at 22:56
  • 1
    I'm looking in N3290 which may be numbered slightly differently. The relevant section is labelled [associative.reqmts] and says amongst other things "For associative containers where the value type is the same as the key type, both iterator and const_iterator are constant iterators." – Alan Stokes Jan 17 '12 at 13:10
  • @AlanStokes Thank you. Please, clarify me with some words about "Your const_cast technically invokes undefined behaviour." – nickolay Jan 17 '12 at 13:42
  • I've reworded slightly; my original statement was overly strong. I suspect that undefined behaviour is invoked when you modify the contained element, because nothing prevents the implementation from holding the elements as `const`, but I'm not totally sure. Modifying the order would definitely be UB, but you're not doing that. – Alan Stokes Jan 17 '12 at 14:07
  • @DaddyM Historical note: C++97 used to guarantee that `set::iterator` was a normal, non-const, iterator... Almost no implementation, except MS, did it that way. The spec was a real mess. – curiousguy Jul 21 '12 at 02:02
  • @AlanStokes "_Modifying the order would definitely be UB_" can you quote the std? – curiousguy Jul 21 '12 at 02:03
  • @curious 23.2.4/3, last sentence. – Alan Stokes Jul 21 '12 at 13:29
  • @AlanStokes Thank you. I was looking at 23.4. I think this is a new requirement that was not in previous spec. – curiousguy Jul 22 '12 at 08:36
  • @curious 23.2.4/5 is even clearer: "Keys in an associative container are immutable." This is indeed new - the omission from the 98 standard was considered a [defect](http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#103). DR 1214 is also relevant. – Alan Stokes Jul 22 '12 at 16:41
4

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.)

Aaron McDaid
  • 24,484
  • 9
  • 56
  • 82
2

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.

John Humphreys - w00te
  • 32,140
  • 30
  • 125
  • 230
0

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.

curiousguy
  • 7,344
  • 2
  • 37
  • 52