42 Intermediate The Hundred
The Map You Cannot Read
a lookup so innocent it doesn't even compile
#include <iostream>
#include <map>
#include <string>
int score_of(const std::map<std::string, int>& scores, const std::string& name) {
return scores[name]; // just reading a value... right?
}
int main() {
const std::map<std::string, int> scores{{"alice", 3}, {"bob", 5}};
std::cout << "alice: " << score_of(scores, "alice") << '\n';
std::cout << "carol: " << score_of(scores, "carol") << '\n';
}
Compile it. What happens?
Answer
It doesn't compile. On a const map, operator[] — the most natural way to read —
is simply not callable.
Why¶
operator[] inserts a default-constructed value when the key is absent (entry 24), so the
standard declares it non-const and gives it no const overload — on a const map there
is nothing left to call. GCC 11 reports that in the vocabulary of member functions rather
than of maps:
error: passing ‘const std::map<std::__cxx11::basic_string<char>, int>’ as ‘this’
argument discards qualifiers [-fpermissive]
…then one note: reprinting operator[]'s signature with every template parameter
substituted. Nothing in the diagnostic mentions insertion: discards qualifiers is the
compiler saying "non-const member, const object." What a const map does offer is
at() and find() — neither can ever add an element, so both carry a const overload,
handing back const int& and const_iterator respectively.
The fix¶
Say what you mean — a pure lookup:
return scores.at(name); // throws std::out_of_range if absent
auto it = scores.find(name); // or, exception-free:
return it != scores.end() ? it->second : 0;
The default build takes the find route and prints alice: 3 then carol: 0.
Takeaway: map::operator[] may insert, so it never works on a const map — read
with at() or find().
Try it: g++ -std=c++17 main.cpp -o demo && ./demo — add -DSHOW_BUG to meet the error.