Цикл for на основе диапазона на unordered_map и ссылках [дубликаты]

При запуске цикла for на основе диапазона на 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 не видит правильный тип, потому что совершенно очевидно, что это ссылочный тип? Или мне не хватает какого-то неопределенного поведения?

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


person Hanno Bänsch    schedule 21.10.2018    source источник
comment
Виновником является decltype: Если аргумент представляет собой выражение id без скобок, именующее структурированное привязка, то decltype возвращает ссылочный тип. Но я признаю, что понятия не имею, в чем причина этого.   -  person HolyBlackCat    schedule 21.10.2018
comment
@HolyBlackCat, чтобы сделать C++ еще более запутанным и менее последовательным. Кажется, это причина всего, что мы добавили за последние годы.   -  person Stephan Dollberg    schedule 21.10.2018


Ответы (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 не создает проблем, даже несмотря на то, что ему не разрешено привязывать ссылку к битовому полю, потому что это псевдоним, а не фактическая ссылка.

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

person T.C.    schedule 21.10.2018
comment
Но это то, что разрешено делать только компилятору? Как пользователь я никогда не смогу определить псевдонимы для переменных, верно? - person Hanno Bänsch; 21.10.2018
comment
Здесь отсутствует указание на то, что делает & в auto&, если не квалифицирует введенные имена как ожидаемый OP: он квалифицирует невидимый объект типа decltype(x), к членам которого относятся эти имена. По сути, существует невидимая ссылка этого типа, и новые имена ссылаются на члены в ней, в результате чего их обновление также обновляет x. - person underscore_d; 21.10.2018