Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Range-based for loop on unordered_map and references [duplicate]

When running a range-based for loop on an std::unordered_map it appears that the type of the loop variable does not use reference types:

std::unordered_map<int, int> map = { {0, 1}, {1, 2}, {2, 3} };
for(auto&[l, r] : map)
    static_assert(std::is_same_v<decltype(r), int&>);

MSVC 2017, gcc 8.2 and clang 7.0.0 all report a failed assertion here. Oppose this to a std::vector, where the assertion does not fail, as one would expect:

std::vector<int> vec = { 1, 2, 3 };
for(auto& r : vec)
    static_assert(std::is_same_v<decltype(r), int&>);

However on both MSVC 2017 and gcc 8.2 a loop modifying the local variable r will have observable side-effects:

#include <iostream>
#include <type_traits>
#include <unordered_map>
#include <vector>

int main() {
    std::unordered_map<int, int> a = { {0, 1}, {1, 2}, {2, 3} };
    for(auto[l, r] : a)
        std::cout << l << "; " << r << std::endl;
    for(auto&[l, r] : a) {
        static_assert(std::is_same_v<decltype(r), int>);
        r++;
    }
    std::cout << "Increment:" << std::endl;
    for(auto[l, r] : a)
        std::cout << l << "; " << r << std::endl;
}

This program for example will print (ignoring order):

0; 1
1; 2
2; 3
Increment:
0; 2
1; 3
2; 4

What am I missing? How can this change the value in the map despite the local variable not being of reference type? Or probably more appropriately, why does std::is_same not see the right type, because quite clearly it is a reference type? Or am I alternatively missing some undefined behavior?

Note that I did reproduce the same issue without using structured bindings, so I keep the nice looking code here. See here for an example

like image 724
Hanno Bänsch Avatar asked Oct 21 '18 11:10

Hanno Bänsch


1 Answers

Structured bindings are modeled as aliases, not "real" references. Even though they may use a reference under the hood.

Imagine that you have

struct X {
    const int first = 0;
    int second;
    int third : 8;
};

X x;
X& y = x;

What's decltype(x.second)? int. What's decltype(y.second)? int. And so in

auto& [first, second, third] = x;

decltype(second) is int, because second is an alias for x.second. And third poses no problems even though it's not allowed to bind a reference to a bit-field, because it's an alias, not an actual reference.

The tuple-like case is designed to be consistent with that. Even though in that case the language has to use references, it does its best to pretend that those references do not exist.

like image 55
T.C. Avatar answered Sep 21 '22 03:09

T.C.