Хорошо ли структурированные привязки и ссылки на пересылку?

Я знаю, что могу

auto&& bla = something();

и в зависимости от const ности возвращаемого значения something, я хотел бы получить другой тип для bla.

Это также работает в случае структурированных привязок, например

auto&& [bla, blabla] = something();

Я бы так догадался (структурированные привязки копируются на auto инициализаторы, которые ведут себя так), но я не могу найти окончательное да.

Обновление: предварительные тесты, похоже, делают то, что я ожидаю (const константу должным образом):

#include <tuple>

using thing = std::tuple<char, int*, short&, const double, const float&>;

int main()
{
    char c = 0;
    int i = 1;
    short s = 2;
    double d = 3.;
    float f = 4.f;

    thing t{c, &i, s, d, f};

    auto&& [cc, ii, ss, dd, ff] = t;

    c = 10;
    *ii = 11;
    ss = 12;
    dd = 13.;
    ff = 14.f;
}

Live demo, дает ошибку, как я ожидал бы, если auto&& выполняет свою работу:

main.cpp: In function 'int main()':
main.cpp:20:10: error: assignment of read-only reference 'dd'
     dd = 13.;
          ^~~
main.cpp:21:10: error: assignment of read-only reference 'ff'
     ff = 14.f;

Мне все равно хотелось бы узнать, где именно указано это поведение.

Примечание. Использование "ссылок на пересылку" означает, что это поведение может растягивать его, но у меня нет хорошего имени, чтобы дать часть const aut auto&& (или template- T&& на то пошло).

Ответ 1

Да. Структурированные привязки и ссылки экспедиционных хорошо перемешайте †.

В общем, в любом месте вы можете использовать auto, вы можете использовать auto&& для получения различного значения. В частности, для структурированных привязок это происходит из [dcl.struct.bind]:

В противном случае e определяется как-если

attribute-specifier-seq opt decl-specifier-seq ref-qualifier opte initializer ;

где объявление никогда не интерпретируется как объявление функции, а части декларации, отличные от идентификатора declarator, берутся из соответствующей декларации структурированного связывания.

Дополнительные ограничения на эти разделы в [dcl.dcl]:

Простая декларация с идентификатором-списком называется объявлением структурированной привязки ([dcl.struct.bind]). Спецификатор decl-specifier-seq должен содержать только auto и-квалификаторы типа-спецификатора. Инициализатор должен иметь вид " = присваивания-выражение", вид " { присвоение выражение } ", либо в форме " ( присваивания-выражение ) ", где присваивание выражение-массив или не накидной класс тип.

Объединяя это, мы можем разбить ваш пример:

auto&& [bla, blabla] = something();

как объявление этой неназванной переменной:

auto               && e = something();
~~~~               ~~     ~~~~~~~~~~~
decl-specifier-seq        initializer
                   ref-qualifier

Поведение - это результат, полученный из [dcl.spec.auto] (особенно здесь). Там мы делаем вывод против инициализатора:

template <typename U> void f(U&& );
f(something());

где auto было заменено на U, а && переносится. Здесь наш справочник. Если дедукция терпит неудачу (что могло бы быть только в случае, если something() было void), наша декларация плохо сформирована. Если это удастся, мы возьмем выведенное U и рассмотрим наше выражение так, как если бы оно было:

U&& e = something();

Который делает e ссылкой lvalue или rvalue, то есть const, не соответствующей, на основе категории стоимости и типа something().

Остальные правила структурированных привязок следуют в [dcl.struct.bind] на основе базового типа e, независимо от того, является ли something() значение lvalue, и является ли e ссылкой на lvalue.


С одной оговоркой. Для структурированного связывания decltype(e) всегда является ссылочным типом, а не типом, который вы могли бы ожидать. Например:

template <typename F, typename Tuple>
void apply1(F&& f, Tuple&& tuple) {
    auto&& [a] = std::forward<Tuple>(tuple);
    std::forward<F>(f)(std::forward<decltype(a)>(a));
}

void foo(int&&);

std::tuple<int> t(42);
apply1(foo, t); // this works!

Я передаю свой tuple - это значение lvalue, которое вы ожидаете передать своим базовым элементам в качестве ссылок lvalue, но они фактически отправляются. Это связано с тем, что decltype(a) - это просто int (ссылочный тип), а не int& (осмысленный способ поведения a). Что-то нужно иметь в виду.

Есть два места, где я могу думать, где это не так.

В объявлениях типа trailing-return-type вы должны использовать только auto. Вы не можете писать, например:

auto&& foo() -> decltype(...);

Единственное другое место, где я могу думать о том, где это может быть не так, - это часть Concepts TS, где вы можете использовать auto в других местах для вывода/ограничения типов. Там, используя ссылку пересылки, когда тип, который вы выводите, не является ссылочным, был бы плохо сформирован. Я думаю:

std::vector<int> foo();
std::vector<auto> a = foo();   // ok, a is a vector<int>
std::vector<auto&&> b = foo(); // error, int doesn't match auto&&