Spec-Zone.ru › C++

Объявление структурированной привязки (с C++17)

Связывает указанные имена с подобъектами или элементами инициализатора.

Как и ссылка, структурированная привязка является псевдонимом существующего объекта. В отличие от ссылки, структурированная привязка не обязана быть типа ссылки.

attr (необязательно) cv-auto ref-qualifier (необязательно) [ identifier-list ] = expression ; (1)
attr (необязательно) cv-auto ref-qualifier (необязательно) [ identifier-list ]{ expression }; (2)
attr (необязательно) cv-auto ref-qualifier (необязательно) [ identifier-list ]( expression ); (3)
attr - последовательность любого количества атрибутов
cv-auto - , возможно, квалифицированный тип спецификатор auto, также может включать спецификатор класса хранения static или thread_local; включение volatile в cv-квалификаторы устарело(с C++20)
ref-qualifier - либо & или &&
identifier-list - список идентификаторов, разделенных запятыми, введенных этим объявлением
expression - выражение, не имеющее оператора запятой на верхнем уровне (грамматически, выражение присваивания), и имеющее массивный или не-объединение класс тип. Если expression ссылается на какие-либо из имен из identifier-list, объявление является некорректным.

Объявление структурированной привязки вводит все идентификаторы в identifier-list как имена в окружающем пространстве имен и связывает их с подобъектами или элементами объекта, обозначаемого expression. Введенные таким образом привязки называются структурированными привязками.

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

  • Если у expression есть тип массива A и нет ref-qualifier, то e имеет тип cv A, где cv — cv-квалификаторы в последовательности cv-auto, и каждый элемент e копируется (для (1)) или напрямую (для (2,3)) инициализируется из соответствующего элемента expression.
  • В противном случае e определяется так, как если бы использовалось его имя вместо [ identifier-list ] в объявлении.

Мы используем E для обозначения типа выражения e. (Другими словами, E эквивалентно std::remove_reference_t<decltype((e))>.)

Объявление структурированной привязки затем выполняет привязку одним из трех возможных способов, в зависимости от E:

  • Случай 1: если E является типом массива, то имена привязываются к элементам массива.
  • Случай 2: если E является типом не-объединение класса и std::tuple_size<E> является полным типом с членом, имеющим имя value (независимо от типа или доступности такого члена), то используется протокол привязки «похожий на кортеж».
  • Случай 3: если E является типом не-объединение класса, но std::tuple_size<E> не является полным типом, то имена привязываются к доступным данным членов E.

Каждый из трех случаев описан более подробно ниже.

Каждая структурированная привязка имеет тип ссылки, определенный в описании ниже. Этот тип является типом, возвращаемым decltype при применении к нескобочной структурированной привязке.

Случай 1: привязка массива

Каждый идентификатор в identifier-list становится именем lvalue, которое ссылается на соответствующий элемент массива. Количество идентификаторов должно совпадать с количеством элементов массива.

Тип ссылки для каждого идентификатора — тип элемента массива. Обратите внимание, что если тип массива E имеет cv-квалификатор, то его тип элемента также имеет cv-квалификатор.

int a[2] = {1, 2};
 
auto [x, y] = a;    // creates e[2], copies a into e,
                    // then x refers to e[0], y refers to e[1]
auto& [xr, yr] = a; // xr refers to a[0], yr refers to a[1]

Случай 2: привязка типа, похожего на кортеж

Выражение std::tuple_size<E>::value должно быть корректным целочисленным константным выражением, а количество идентификаторов должно быть равно std::tuple_size<E>::value.

Для каждого идентификатора вводится переменная, тип которой — «ссылка на std::tuple_element<i, E>::type»: ссылка lvalue, если соответствующий инициализатор — lvalue, ссылка rvalue в противном случае. Инициализатор для i-й переменной:

  • e.get<i>(), если поиск идентификатора get в области видимости E с помощью поиска члена класса находит, по крайней мере, одно объявление, которое является шаблоном функции, у которого первый параметр шаблона является параметром нетипа
  • В противном случае, get<i>(e), где get ищется только с помощью поиска зависимого от аргумента, игнорируя поиск, не относящийся к ADL.

В этих выражениях инициализации e является lvalue, если тип сущности e — ссылка lvalue (это происходит только в том случае, если ref-qualifier — & или если это && и выражение инициализации является lvalue), и xvalue в противном случае (это эффективно выполняет своего рода идеальное ретранслирование), i — std::size_t prvalue, а <i> всегда интерпретируется как список параметров шаблона.

Переменная имеет тот же срок существования, что и e.

Идентификатор затем становится именем lvalue, который ссылается на объект, привязанный к данной переменной.

Тип ссылки для i-го идентификатора — std::tuple_element<i, E>::type.

float x{};
char  y{};
int   z{};
 
std::tuple<float&, char&&, int> tpl(x, std::move(y), z);
const auto& [a, b, c] = tpl;
// using Tpl = const std::tuple<float&, char&&, int>;
// a names a structured binding that refers to x (initialized from get<0>(tpl))
// decltype(a) is std::tuple_element<0, Tpl>::type, i.e. float&
// b names a structured binding that refers to y (initialized from get<1>(tpl))
// decltype(b) is std::tuple_element<1, Tpl>::type, i.e. char&&
// c names a structured binding that refers to the third component of tpl, get<2>(tpl)
// decltype(c) is std::tuple_element<2, Tpl>::type, i.e. const int

Случай 3: привязка к членам данных

Каждый нестатический член данных E должен быть прямым членом E или того же базового класса E, и должен быть корректным в контексте структурированной привязки при именовании как e.name. E не может иметь анонимного члена объединения. Количество идентификаторов должно быть равно количеству нестатических членов данных.

Каждый идентификатор в identifier-list становится именем lvalue, которое ссылается на следующий член e в порядке объявления (поддерживаются битовые поля); тип lvalue — это тип e.m_i, где m_i относится к i-му члену.

Тип ссылки i-го идентификатора — это тип e.m_i если это не тип ссылки, или объявленный тип m_i в противном случае.

#include <iostream>
 
struct S
{
    mutable int x1 : 2;
    volatile double y1;
};
 
S f() { return S{1, 2.3}; }
 
int main()
{
    const auto [x, y] = f(); // x is an int lvalue identifying the 2-bit bit-field
                             // y is a const volatile double lvalue
    std::cout << x << ' ' << y << '\n';  // 1 2.3
    x = -2;   // OK
//  y = -2.;  // Error: y is const-qualified
    std::cout << x << ' ' << y << '\n';  // -2 2.3
}

Примечания

Структурированные привязки не могут быть ограничены:

template<class T>
concept C = true;
 
C auto [x, y] = std::pair{1, 2}; // error: constrained
(с C++20)

Поиск члена get игнорирует доступность как обычно, а также игнорирует точный тип параметра шаблона нетипа. Приватный template<char*> void get(); член приведет к использованию интерпретации члена, даже если это некорректно.

Часть объявления, предшествующая [ относится к скрытой переменной e, а не к введенным идентификаторам:

int a = 1, b = 2;
const auto& [x, y] = std::tie(a, b); // x and y are of type int&
auto [z, w] = std::tie(a, b);        // z and w are still of type int&
assert(&z == &a);                    // passes

Интерпретация, похожая на кортеж, всегда используется, если std::tuple_size<E> — это полный тип с членом, имеющим имя value, даже если это приведет к некорректной программе:

struct A { int x; };
 
namespace std
{
    template<>
    struct tuple_size<::A> { void value(); };
}
 
auto [x] = A{}; // error; the "data member" interpretation is not considered.

Обычные правила привязки ссылок к временным объектам (включая расширение срока жизни) применяются, если присутствует ref-qualifier, а expression — prvalue. В этих случаях скрытая переменная e — ссылка, которая привязывается к временной переменной материализованной из выражения prvalue, увеличивая ее срок жизни. Как обычно, привязка не удастся, если e — неконстантная ссылка lvalue:

int a = 1;
 
const auto& [x] = std::make_tuple(a); // OK, not dangling
auto&       [y] = std::make_tuple(a); // error, cannot bind auto& to rvalue std::tuple
auto&&      [z] = std::make_tuple(a); // also OK

decltype(x), где x обозначает структурированную привязку, называет тип ссылки этой структурированной привязки. В случае, подобном кортежу, это тип, возвращаемый std::tuple_element, который может не быть ссылкой, даже если скрытая ссылка всегда вводится в этом случае. Это эффективно эмулирует поведение привязки к структуре, члены данных которой имеют типы, возвращаемые tuple_element, где ссылка привязки сама по себе является всего лишь деталью реализации.

std::tuple<int, int&> f();
 
auto [x, y] = f();       // decltype(x) is int
                         // decltype(y) is int&
 
const auto [z, w] = f(); // decltype(z) is const int
                         // decltype(w) is int&

Структурированные привязки не могут быть захвачены лямбда-выражениями:

#include <cassert>
 
int main()
{
    struct S { int p{6}, q{7}; };
    const auto& [b, d] = S{};
    auto l = [b, d] { return b * d; }; // valid since C++20
    assert(l() == 42);
}
(до C++20)
Feature-test macro Value Std Feature
__cpp_structured_bindings 201606L (C++17) Структурированные привязки

Пример

#include <iomanip>
#include <iostream>
#include <set>
#include <string>
 
int main()
{
    std::set<std::string> myset{"hello"};
 
    for (int i{2}; i; --i)
    {
        if (auto [iter, success] = myset.insert("Hello"); success) 
            std::cout << "Insert is successful. The value is "
                      << std::quoted(*iter) << ".\n";
        else
            std::cout << "The value " << std::quoted(*iter)
                      << " already exists in the set.\n";
    }
 
    struct BitFields
    {
        // C++20: default member initializer for bit-fields
        int b : 4 {1}, d : 4 {2}, p : 4 {3}, q : 4 {4};
    };
 
    {
        const auto [b, d, p, q] = BitFields{};
        std::cout << b << ' ' << d << ' ' << p << ' ' << q << '\n';
    }
 
    {
        const auto [b, d, p, q] = []{ return BitFields{4, 3, 2, 1}; }();
        std::cout << b << ' ' << d << ' ' << p << ' ' << q << '\n';
    }
 
    {
        BitFields s;
 
        auto& [b, d, p, q] = s;
        std::cout << b << ' ' << d << ' ' << p << ' ' << q << '\n';
 
        b = 4, d = 3, p = 2, q = 1;
        std::cout << s.b << ' ' << s.d << ' ' << s.p << ' ' << s.q << '\n';
    }
}

Вывод:

Insert is successful. The value is "Hello".
The value "Hello" already exists in the set.
1 2 3 4
4 3 2 1
1 2 3 4
4 3 2 1

Отчёты об ошибках

Ниже приведены отчёты об ошибках, изменяющие поведение, которые были применены ретроактивно к ранее опубликованным стандартам C++.

Отчёт об ошибке Применимо к Поведение при публикации Правильное поведение
CWG 2285 C++17 выражение могло ссылаться на имена из списка идентификаторов в этом случае объявление некорректно
CWG 2312 C++17 значение mutable было утеряно
в случае привязки к членам
его значение сохраняется
CWG 2386 C++17 протокол привязки «по типу кортежа» используется
всякий раз, когда tuple_size<E> является полным типом
используется только тогда, когда tuple_size<E>
имеет член value
CWG 2635 C++20 связанные структуры могли быть ограничены запрещено
P0961R1 C++17 в случае «по типу кортежа», член get используется,
если поиск находит get любого типа
только если поиск находит шаблон функции
с параметром без типа
P0969R0 C++17 в случае привязки к членам, члены должны быть публичными требуется только доступность
в контексте объявления

Ссылки

  • Стандарт C++23 (ISO/IEC 14882:2023):
    • 9.6 Объявления связанных структур [dcl.struct.bind] (с. 228-229)
  • Стандарт C++20 (ISO/IEC 14882:2020):
    • 9.6 Объявления связанных структур [dcl.struct.bind] (с. 219-220)
  • Стандарт C++17 (ISO/IEC 14882:2017):
    • 11.5 Объявления связанных структур [dcl.struct.bind] (с. 219-220)

См. также

tie
(C++11)
создает tuple из ссылок на lvalues или распаковывает кортеж в отдельные объекты
(шаблон функции)

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/w/cpp/language/structured_binding

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API