Spec-Zone.ru › C++

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

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

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

attr (необязательно) decl-specifier-seq ref-qualifier (необязательно) [ sb-identifier-list ] initializer ;
attr - последовательность из любого количества атрибутов
decl-specifier-seq - последовательность следующих спецификаторов (согласно правилам простого объявления):
  • constexpr(необязательно)
  • constinit(необязательно)
(начиная с C++26)
  • static(необязательно)
  • thread_local(необязательно)
  • const(необязательно)
  • volatile(необязательно) (устарел в C++20)
  • auto
ref-qualifier - либо &, либо &&
sb-identifier-list - список идентификаторов, разделённых запятыми и вводимых этим объявлением; за каждым идентификатором может следовать последовательность спецификаторов атрибутов(начиная с C++26)
initializer - инициализатор (см. ниже)


initializer может иметь одну из следующих форм:

= expression (1)
{ expression } (2)
( expression ) (3)
expression - любое выражение (кроме не заключённых в скобки выражений с оператором «запятая»)


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

Перед одним из идентификаторов в sb-identifier-list можно поставить многоточие. Такой идентификатор вводит пакет структурированных связываний.

Идентификатор должен объявлять шаблонную сущность.

(начиная с C++26)

Структурированное связывание — это идентификатор из sb-identifier-list , перед которым не стоит многоточие, либо элемент пакета структурированных связываний, введённого в том же списке идентификаторов(начиная с C++26).

Процесс связывания

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

  • Если expression имеет тип массива cv1 A и ref-qualifier отсутствует, определить e как attr (optional) specifiers A e;, где specifiers — последовательность спецификаторов из decl-specifier-seq, за исключением auto.
Затем каждый элемент e инициализируется соответствующим элементом expression согласно форме initializer :
  • Для синтаксиса инициализатора (1) элементы инициализируются копированием.
  • Для синтаксиса инициализатора (2,3) элементы инициализируются напрямую.
  • В противном случае определить e как attr (optional) decl-specifier-seq ref-qualifier (optional) e initializer ;.

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

Размер структурированного связывания для E — это количество структурированных связываний, которые должно ввести объявление структурированного связывания.

Количество идентификаторов в sb-identifier-list должно быть равно размеру структурированного связывания для E.

(до C++26)

Обозначим количество идентификаторов в sb-identifier-list как N, а размер структурированного связывания для E — как S:

  • Если пакет структурированных связываний отсутствует, N должно быть равно S.
  • В противном случае количество элементов, не входящих в пакет (то есть N - 1), должно быть меньше или равно S, а количество элементов пакета структурированных связываний равно S - N + 1 (оно может быть нулевым).
(начиная с C++26)
struct C { int x, y, z; };

template<class T>
void now_i_know_my() 
{
    auto [a, b, c] = C(); // OK: a, b, c refer to x, y, z, respectively
    auto [d, ...e] = C(); // OK: d refers to x; ...e refers to y and z
    auto [...f, g] = C(); // OK: ...f refers x and y; g refers to z
    auto [h, i, j, ...k] = C();    // OK: the pack k is empty
    auto [l, m, n, o, ...p] = C(); // error: structured binding size is too small
}

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

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

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

У каждого структурированного связывания есть связанный тип, определённый ниже. Этот тип возвращается выражением decltype, применённым к структурированному связыванию без скобок.

Случай 1: связывание с массивом

Каждое структурированное связывание в sb-identifier-list становится именем lvalue, ссылающегося на соответствующий элемент массива. Размер структурированного связывания для E равен количеству элементов массива.

Связанный тип каждого структурированного связывания — это тип элемента массива. Обратите внимание: если тип массива 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 должно быть корректным целочисленным константным выражением, а размер структурированного связывания для E равен std::tuple_size<E>::value.

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

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

В этих выражениях-инициализаторах e является lvalue, если тип сущности e — ссылка lvalue (это происходит только в том случае, если ref-qualifier равен & или равен &&, а выражение-инициализатор является lvalue), и xvalue в противном случае (это фактически выполняет разновидность идеальной передачи); I — prvalue типа std::size_t, а <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 не может иметь анонимного члена-объединения. Размер структурированного связывания для E равен количеству нестатических полей данных.

Каждое структурированное связывание в sb-identifier-list становится именем lvalue, ссылающегося на следующее поле e в порядке объявления (поддерживаются битовые поля); тип lvalue совпадает с типом e.mI, где mI обозначает поле с номером I.

Связанный тип структурированного связывания с номером I — это тип e.mI, если он не является ссылочным типом, и объявленный тип mI в противном случае.

#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
}

Порядок инициализации

Пусть valI — объект или ссылка, названные структурированным связыванием с номером I в sb-identifier-list :

  • Инициализация e упорядочена перед инициализацией любого valI.
  • Инициализация каждого valI упорядочена перед инициализацией любого valJ, где I меньше J.

Примечания

Структурированные связывания нельзя ограничивать:

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, который может не быть ссылкой, хотя в этом случае всегда вводится скрытая ссылка. Это фактически имитирует поведение связывания со структурой, нестатические поля данных которой имеют типы, возвращаемые std::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)

Размер структурированного связывания может быть равен 0, если sb-identifier-list содержит ровно один идентификатор, который может ввести только пустой пакет структурированных связываний.

auto return_empty() -> std::tuple<>;

template <class>
void test_empty()
{
    auto [] = return_empty(); // error
    auto [...args] = return_empty(); // OK, args is an empty pack
    auto [one, ...rest] = return_empty(); // error, structured binding size is too small
}
(начиная с C++26)
Макрос проверки поддержки возможностей Значение Стандарт Возможность
__cpp_structured_bindings 201606L (C++17) Структурированные связывания
202403L (C++26) Структурированные связывания с атрибутами
202406L (C++26) Объявление структурированного связывания в качестве условия
202411L (C++26) Структурированные связывания могут вводить пакет

Ключевые слова

auto

Пример

#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++.

DR Применён к Опубликованное поведение Корректное поведение
CWG 2285 C++17 expression могло ссылаться на имена из identifier-list в этом случае объявление
некорректно
CWG 2312 C++17 в случае 3 терялось значение mutable его значение сохраняется
CWG 2313 C++17 в случае 2 переменные структурированного связывания можно было объявить повторно повторное объявление невозможно
CWG 2339 C++17 в случае 2 отсутствовало определение I определение добавлено
CWG 2341
(P1091R3)
C++17 структурированные связывания нельзя было
объявлять со статической длительностью хранения
разрешено
CWG 2386 C++17 протокол связывания «подобно кортежу» использовался
всякий раз, когда std::tuple_size<E> является полным типом
используется только тогда, когда у std::tuple_size<E>
есть член value
CWG 2506 C++17 если expression имеет cv-квалифицированный тип массива,
cv-квалификация переносилась на E
эта cv-квалификация отбрасывается
CWG 2635 C++20 структурированные связывания можно было ограничивать запрещено
CWG 2867 C++17 порядок инициализации был неясен уточнён
P0961R1 C++17 в случае 2 использовался член get,
если поиск находил get любого вида
только если поиск находит шаблон
функции с нетиповым параметром
P0969R0 C++17 в случае 3 поля должны были быть открытыми требуется только доступность
в контексте объявления
P1306R5 C++17 размер структурированного связывания был неясен, если E имеет тип массива с неизвестной границей граница массива должна быть известна

Ссылки

  • Стандарт C++23 (ISO/IEC 14882:2024):
    • 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 из ссылок lvalue или распаковывает кортеж в отдельные объекты
(шаблон функции)

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

Spec-Zone.ru

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