20

I have the following issue:

std::map<A*,double> map;

void getColor(A const * obj){
    double d = map[obj]; // does not compile wihtout const_cast<A*>(obj)
    // do something
}

I have a map std::map (somewhere) which stores pointers to objects A. I have a function getColor which does not manipulate objects A and so takes a pointer to a const A as input.

The function getColor will not compile without using a const_cast.

The const cast is a design issue, but I don't know how to circumvent it if I don't want to make the keys in map const.

Any help appreciated.

alfakini
  • 4,635
  • 2
  • 26
  • 35
Gabriel
  • 8,990
  • 6
  • 57
  • 101

2 Answers2

19

There's two possible scenarios here:

  1. The function knows/expects that obj is already present in the map, and you're using [] for convenience.

  2. You're using [] for its full potential, i.e. you expect it to add obj to the map if not already there.

In situation 2, you have an error in the getColor signature. Since it can potentially pass obj to a place where it will be stored as A*, it is wrong for it to accept a const A* only. Note that even if a function doesn't modify an object itself but passes it on somewhere where it can be modified, it is effectively modifying it indirectly and should therefore take it as non-const.

In situation 1, it depends on your C++ version. C++14 introduced a template overload of find and related member functions of std::map which takes anything comparable with Key instead of only Key. You could therefore modify the function like this:

void getColor( A const * obj){
    doubel d = map.find(obj)->second;
    // do something
}

Note that for this to work, you also need to change the map's type to use a transparent comparator: std::map<A*,double, std::less<>> map; (as first pointed out by @Leon's answer).

If you're stuck with C++11 or earlier, you're out of luck and you'll have to live with the const_cast. Note that with a suitable comment, a const_cast is perfectly safe and acceptable in this case (not to mention the only way to proceed without changing the type of map). Again, you should use find or perhaps at instead of [], since you do not want to insert into the map.

Community
  • 1
  • 1
Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • Does `less<>` do the guaranteed right thing when passed pointers? – Yakk - Adam Nevraumont Nov 24 '16 at 14:10
  • @Yakk Yes, unlike `operator <`, `std::less` is guaranteed to work even for pointers to unrelated objects. – Angew is no longer proud of SO Nov 24 '16 at 14:14
  • 1
    @angew "the specializations for any pointer type yield a total order, even if the built-in operators `<`, `>`, `<=`, `>=` do not. For template specializations `greater`, `less`, `greater_equal`, and `less_equal`, if the call operator calls a built-in operator comparing pointers, the call operator yields a total order." ok, the standard is clear here! – Yakk - Adam Nevraumont Nov 24 '16 at 14:17
  • 3
    @Yakk Fun fact: until [very recently](https://timsong-cpp.github.io/lwg-issues/2562), the wording doesn't actually guarantee that the total orders imposed by those is consistent with the partial order imposed by the built-ins or with each other :) – T.C. Nov 24 '16 at 19:18
6

If you can afford switching to C++14 then you can configure your map to use a transparent comparator (this will work since a const pointer can be compared with a non-const pointer):

std::map<A*,double, std::less<>> map;
//                  ^^^^^^^^^^^
//                  enable transparent comparator on this map

void getColor( A const * obj){
    auto it = map.find(obj);
    assert(it != map.end());
    double d = it->second;
    // do something
}

Note that you will have to use std::map::find() instead of std::map::operator[], since the latter doesn't have a transparent version.

Community
  • 1
  • 1
Leon
  • 31,443
  • 4
  • 72
  • 97