Объявление структурированной привязки (с 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имеет тип cvA, где 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)
См. также
|
(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