Ответ 1
Рассмотрим случай:
std::tuple<int, int&>& foo();
auto& [x, y] = foo();
Что такое decltype(x)
и что такое decltype(y)
? Цель языковой функции состоит в том, что x
просто будет другим именем для foo().__0
и y
- другое имя для foo().__1
, что означает, что они должны быть int
и int&
соответственно. Как specificied сегодня, это распаковывает в †:
auto& __e = foo();
std::tuple_element_t<0, decltype(__e)>& x = std::get<0>(__e);
std::tuple_element_t<1, decltype(__e)>& y = std::get<1>(__e);
И правила работают так, что decltype(x)
- это тип, к которому относится x
, поэтому int
. И decltype(y)
- это тип, к которому относится y
, поэтому int&
.
Если мы tuple_element
, tuple_element
что-то вроде:
auto&& x = std::get<0>(__e);
auto&& y = std::get<1>(__e);
Тогда мы не могли бы различать x
и y
, потому что нет возможности различать то, что std::get<0>(__e)
и std::get<1>(__e)
: оба возвращают int&
.
Это также способ добавить согласованность между указанным выше случаем и нормальным случаем структуры:
struct C {
int i;
int& r;
};
C& bar();
auto& [a, b] = bar();
Мы хотим, чтобы в целях структурированных привязок для a
и b
здесь вести себя так же, как x
и y
. И и a
b
здесь не введены переменные, они просто разные названия для __e.i
и __e.r
.
В случае без ссылки существует другой сценарий, в котором мы не можем дифференцировать:
std::tuple<int, int&&> foo();
auto [x, y] = foo();
Здесь мы в настоящее время распаковываем через:
auto __e = foo();
std::tuple_element_t<0, decltype(e)>& x = std::get<0>(std::move(__e));
std::tuple_element_t<1, decltype(e)>& y = std::get<1>(std::move(__e));
Оба вызова std::get
возвращают int&&
, поэтому вы не можете различать их с помощью auto&&
... но результаты tuple_element_t
различаются - int
и int&&
соответственно. Это различие можно было наблюдать и в случае с обычной структурой.
† Обратите внимание, что из-за CWG 2313 фактически распаковка происходит в ссылку с уникальным именем переменной, а идентификаторы, указанные в привязке, относятся только к этим объектам.