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

Я изучаю объявления структурированной привязки. Насколько я понял, в auto& [x, y] = expr; введены переменные x и y типа "ссылка на std::tuple_element<i, E>::type" (ибо i=0, 1 и E это тип невидимой переменной e). Более того, эти переменные инициализируются с помощью get<i>(e).

Итак, если я использую auto&, а get<> возвращает значение (не ссылку), оно не должно компилироваться, так как вы не можете привязать lvalue к временному. Однако следующий пример создается для меня в некоторых версиях GCC, Clang и Visual Studio:

#include <cstddef>
#include <tuple>
#include <type_traits>

struct Foo {
    template<std::size_t i>
    int get() { return 123; }
};

namespace std {
    template<> struct tuple_size<Foo> : integral_constant<size_t, 1> {};
    template<std::size_t i> struct tuple_element<i, Foo> { using type = int; };
}

int main() {
    Foo f;
    auto& [x] = f;
    x++;
}

Более того, C++ Insights ясно показывает, что clang расширяет структурированную привязку до:

Foo f = Foo();
Foo & __f17 = f;
std::tuple_element<0, Foo>::type x = __f17.get<0>();
x++;

Здесь он объявляет x не как ссылку, а как значение. Почему это?

Я ожидал ссылки на lvalue и ошибку компиляции: e (__f17 в приведенном выше примере) — это ссылка на lvalue.


person yeputons    schedule 21.04.2020    source источник


Ответы (1)


Это связано с тем, что auto& не применяется к структурированным привязкам. Он применяется к базовому объекту, который ссылается на структуру. В вашем фрагменте cppinsights это будет __f17.

Если бы вместо этого вы использовали auto [x], фрагмент кода расширился бы до чего-то вроде этого

Foo f = Foo();
Foo __f17 = f; // Difference here
std::tuple_element<0, Foo>::type x = __f17.get<0>();
x++;

Сами привязки всегда являются своего рода ссылкой на базовый объект. Однако код cppinsights не совсем точно отражает это. Соответствующие отрывки в стандарте С++ говорят об этом

[dcl.struct.bind]

3 В противном случае, если квалифицированный идентификатор std​::​tuple_­size<E> называет полное тип, выражение std​::​tuple_­size<E>​::​value должно быть правильно сформированным целочисленным постоянным выражением, а количество элементов в списке-идентификаторов должно быть равно значению этого выражения. Неквалифицированный идентификатор get просматривается в области E с помощью поиска доступа к члену класса, и если это находит хотя бы одно объявление, инициализатором является e.get<i>(). В противном случае инициализатором является get<i>(e), где get ищется в связанных пространствах имен. В любом случае get<i> интерпретируется как идентификатор шаблона. [ Примечание. Обычный неквалифицированный поиск не выполняется. — конец примечания ] В любом случае e является lvalue, если тип сущности e является ссылкой lvalue, и xvalue в противном случае. Учитывая тип Ti, обозначенный std​::​tuple_­element<i, E>​::​type, каждый vi является переменной типа «ссылка на Ti», инициализированной инициализатором, где ссылка является ссылкой lvalue, если инициализатор является lvalue, и ссылкой rvalue в противном случае; указанный тип - Ti.

person StoryTeller - Unslander Monica    schedule 21.04.2020
comment
Хотя это имеет смысл, разве в цитируемом тексте не говорится, что vi должно быть ссылкой (на что-то в этом базовом e, независимо от того, является ли это ссылкой или нет)? - person Lapshin Dmitry; 21.04.2020
comment
@LapshinDmitry - Да. Как я уже упоминал, CPP Insights не отражает это правильно. - person StoryTeller - Unslander Monica; 21.04.2020
comment
Если привязки являются ссылками на базовый объект, как они могут быть привязаны к временному объекту? Они должны быть ссылками на lvalue, потому что __f17 является lvalue. - person yeputons; 21.04.2020
comment
Lvalue-ность __f17 имеет к этому мало отношения. Инициализатор для привязки — __f17.get<0>(), это rvalue, поэтому тип ссылки — это ссылка rvalue (четко указано в тексте). Что касается привязки ссылок к временным объектам, знакомы ли вы с продлением срока действия ссылки< /а>? Вот как. - person StoryTeller - Unslander Monica; 21.04.2020
comment
@LapshinDmitry - это причуда decltype stackoverflow. ком/вопросы/60830650/ - person StoryTeller - Unslander Monica; 21.04.2020
comment
@StoryTeller-UnslanderМоника Ого, спасибо. Хотя зачем это делать? Притвориться, что это на самом деле элемент кортежа? - person Lapshin Dmitry; 21.04.2020
comment
@LapshinDmitry - я могу только предполагать. Я предполагаю, что это делается для того, чтобы структурированные привязки больше походили на обычные переменные, а не на замаскированные ссылки, поскольку их объявления более или менее похожи на обычные объекты. - person StoryTeller - Unslander Monica; 21.04.2020
comment
Ах я вижу. Я думал, что инициализатор, где ссылка является ссылкой lvalue, если инициализатор является lvalue, а ссылка rvalue в противном случае почему-то ссылается на инициализатор e. - person yeputons; 21.04.2020