Skip to content

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.

Open in Compiler Explorer ↗ Open SHOW_BUG variant ↗ Quiz this entry