Диапазон для цикла для unordered_map и ссылок

При запуске цикла, основанного на диапазоне, на std :: unordered_map, кажется, что тип переменной цикла не использует ссылочные типы:

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 и clang 7.0.0 все сообщают о неудавшемся утверждении. Противодействуйте этому std :: vector, где утверждение не терпит неудачу, как и следовало ожидать:

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

Однако на обоих MSVC 2017 и gcc 8.2 цикл, изменяющий локальную переменную r, будет иметь наблюдаемые побочные эффекты:

#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;
}

Эта программа, например, будет печатать (игнорируя порядок):

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

Что мне не хватает? Как это может изменить значение на карте, несмотря на то, что локальная переменная не является ссылочным типом? Или, вероятно, более правильно, почему std :: is_same не видит нужного типа, потому что вполне понятно, что это ссылочный тип? Или я, альтернативно, пропущу какое-то неопределенное поведение?

Обратите внимание, что я воспроизвел одну и ту же проблему без использования структурированных привязок, поэтому я сохраняю красивый код здесь. См. Здесь пример

Ответ 1

Структурированные привязки моделируются как псевдонимы, а не "реальные" ссылки. Даже если они могут использовать ссылку под капотом.

Представьте, что у вас есть

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

X x;
X& y = x;

Какой decltype(x.second)? int. Какой decltype(y.second)? int. И так в

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

decltype(second) - int, потому что second является псевдонимом для x.second. И third представляет проблем, даже если ему не разрешено связывать ссылку на бит-поле, потому что это псевдоним, а не фактическая ссылка.

Случай, подобный кортежу, призван соответствовать этому. Хотя в этом случае язык должен использовать ссылки, он делает все возможное, чтобы притворяться, что эти ссылки не существуют.