Объявление структурированного связывания (начиная с C++17)
Связывает указанные имена с подобъектами или элементами инициализатора.
Как и ссылка, структурированное связывание является псевдонимом существующего объекта. В отличие от ссылки, структурированное связывание не обязано иметь ссылочный тип.
attr (необязательно) decl-specifier-seq ref-qualifier (необязательно) [ sb-identifier-list ] initializer ; |
| attr | - | последовательность из любого количества атрибутов | ||
| decl-specifier-seq | - | последовательность следующих спецификаторов (согласно правилам простого объявления):
|
||
| 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 должно быть равно размеру структурированного связывания для | (до C++26) |
| Обозначим количество идентификаторов в sb-identifier-list как
| (начиная с 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) |
| Размер структурированного связывания может быть равен 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) | Структурированные связывания могут вводить пакет |
Ключевые слова
Пример
#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)
См. также
|
(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