Skip to content

45 Intermediate The Hundred

The Loop That Copied the Whole Map

the & that doesn't save you a thing

int main() {
    std::map<std::string, int> m{{"maximum-connection-retries", 3}};
    const auto& stored = *m.begin();   // the element actually living in the map

    std::cout << std::boolalpha << std::left;

    for (const std::pair<std::string, int>& e : m)
        std::cout << "pair<std::string, int>&  same value? " << std::setw(8)
                  << (&e.second == &stored.second) << "same key buffer? "
                  << (e.first.data() == stored.first.data()) << '\n';

    for (const auto& e : m)
        std::cout << "auto&                    same value? " << std::setw(8)
                  << (&e.second == &stored.second) << "same key buffer? "
                  << (e.first.data() == stored.first.data()) << '\n';
}

Both loops bind e by const&. Do both print true?

Answer
pair<std::string, int>&  same value? false   same key buffer? false
auto&                    same value? true    same key buffer? true

The first loop never sees the map's elements at all — it copies each one.

Why

A std::map<K, V>'s value_type is std::pair<const K, V>, not std::pair<K, V> — the key is const so you can't quietly break the ordering invariant. That makes const std::pair<std::string, int>& a reference to a different type, which triggers the oldest rule in the book: a const& that can't bind directly gets a converted temporary to bind to instead. So each iteration copy-constructs a whole pair, key string and all; over a 1000-entry map with long keys that is 1000 heap allocations to read 1000 ints, where the auto& loop makes zero. Happily, GCC 11 does flag this under plain -Wall:

warning: loop variable ‘e’ of type ‘const std::pair<std::__cxx11::basic_string<char>, int>&’
         binds to a temporary constructed from type
         ‘std::pair<const std::__cxx11::basic_string<char>, int>’ [-Wrange-loop-construct]

The fix

Let the compiler spell the element type, or spell it exactly:

for (const auto& e : m)                                 // value_type&, no copy
for (const auto& [key, count] : m)                      // C++17, same thing, nicer names
for (const std::pair<const std::string, int>& e : m)    // the honest long form

Takeaway: a map element is pair<const K, V> — say const auto& and let the compiler get the const right for you.

Try it: g++ -std=c++17 main.cpp -o demo && ./demo — add -Wall to get the warning.

Open in Compiler Explorer ↗ Quiz this entry