Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does const std::pair<K,V>& in range-based for loop on std::map not work? [duplicate]

When accessing the elements of a std::map via const auto& entry in a range-based for loop I get a reference to the actual data in the map. Using const std::pair<K,V>& on the other hand does not give a reference to the data in the std::map

Consider this example (compiled with gcc 7.4, -std=c++14)

#include <map>
#include <string>
#include <iostream>

int main(void)
{
    std::map<std::string, int> my_map {{"foo", 42}};
    for(const auto& entry : my_map)
        std::cout << entry.first << ' ' << entry.second << ' ' << &(entry.second) << std::endl;
    for(const std::pair<std::string, int>& entry : my_map)
        std::cout << entry.first << ' ' << entry.second << ' ' << &(entry.second) << std::endl;
    return 0;
}

Output:

foo 42 0x11a7eb0
foo 42 0x7ffec118cfc0

I am aware that the std::map value_type is std::pair<const Key, T>. But I don't really understand what is happening in the case of the second range-based loop.

like image 452
SPA Avatar asked Jul 12 '20 16:07

SPA


2 Answers

std::map<K, V>::value_type is std::pair<const K, V>, not std::pair<K, V> (see cppreference)

#include <map>
#include <string>
#include <iostream>

int main(void)
{
    std::map<std::string, int> my_map {{"foo", 42}};
    for(const auto& entry : my_map)
        std::cout << entry.first << ' ' << entry.second << ' ' << &(entry.second) << std::endl;
    for(const std::pair<std::string, int>& entry : my_map)
        std::cout << entry.first << ' ' << entry.second << ' ' << &(entry.second) << std::endl;
    for(const std::pair<const std::string, int>& entry : my_map)
        std::cout << entry.first << ' ' << entry.second << ' ' << &(entry.second) << std::endl;
    return 0;
}

Example output:

foo 42 0x2065eb0
foo 42 0x7ffc2d536070
foo 42 0x2065eb0

Your second loop works because it's creating a temporary std::pair<std::string, int> and binding it to your reference (explanation). You can see it fail if you try to use a non-const reference instead (since it can't bind to a temporary):

error: invalid initialization of reference of type 'std::pair<std::__cxx11::basic_string<char>, int>&' from expression of type 'std::pair<const std::__cxx11::basic_string<char>, int>'

like image 112
Kevin Avatar answered Nov 06 '22 01:11

Kevin


As you mentioned, the types inside the std::map are std::pair<const Key, T>, not std::pair<Key, T>. This means that when we iterate over the map and pull out const std::pair<Key, T>&s, we can't get a reference to the element, but something else is happening. This something else is lifetime extension. This loop:

for(const std::pair<std::string, int>& entry : my_map) {
    ...
}

Is roughly equivalent to this loop:

for(const std::pair<std::string, int> entry : my_map) {
    ...
}

So you're actually getting a copy of every entry in the map.

like image 27
Justin Avatar answered Nov 06 '22 00:11

Justin