Почему структурированные привязки зависят от tuple_element?

Самый последний проект предложения структурированных привязок (на котором была основана функция С++ 17) требует std::tuple_size, member get или std::get и std::tuple_element. Предыдущие черновики требуют только std::tuple_size и member get или std::get. Насколько я могу судить, дискуссия о добавлении этого не обсуждалась, она появилась только в окончательном проекте. Есть ли веская причина требовать специализацию tuple_element, учитывая, что я считаю, что она может быть реализована в целом как

template<std::size_t index, typename T>
struct tuple_element {
    using type = decltype(std::get<index>(std::declval<T>()));
};

Кто-нибудь знает, почему это требование было добавлено?

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