Structured binding declaration (since C++17)
Vincula los nombres especificados a subobjetos o elementos del inicializador.
Al igual que una referencia, un enlace estructurado es un alias para un objeto existente. A diferencia de una referencia, un enlace estructurado no tiene que ser de tipo referencia.
attr
(opcional)
decl-specifier-seq
ref-qualifier
(opcional)
[
sb-identifier-list
]
initializer
;
|
|||||||||
| attr | - | secuencia de cualquier número de atributos | ||
| decl-specifier-seq | - |
secuencia de los siguientes especificadores (siguiendo las reglas de
declaración simple
):
|
||
| ref-qualifier | - |
ya sea
&
o
&&
|
||
| sb-identifier-list | - | lista de identificadores separados por comas introducidos por esta declaración , cada identificador puede ir seguido de una secuencia de especificadores de atributos (desde C++26) | ||
| initializer | - | un inicializador (ver abajo) |
initializer
puede ser uno de los siguientes:
=
expresión
|
(1) | ||||||||
{
expresión
}
|
(2) | ||||||||
(
expresión
)
|
(3) | ||||||||
| expression | - | cualquier expresión (excepto expresiones de coma sin paréntesis) |
Una declaración de enlace estructurado introduce todos los identificadores en la
sb-identifier-list
como nombres en el ámbito circundante y los vincula a subobjetos o elementos del objeto denotado por
expression
. Los enlaces así introducidos se denominan
enlaces estructurados
.
|
Uno de los identificadores en la lista-de-identificadores-sb puede ir precedido por una elipsis. Dicho identificador introduce un paquete de enlace estructurado . El identificador debe declarar una entidad con plantilla . |
(since C++26) |
Un enlace estructurado es un identificador en la lista-de-identificadores-sb que no está precedido por una elipsis, o un elemento de un paquete de enlace estructurado introducido en la misma lista de identificadores (desde C++26) .
Contenidos |
Proceso de vinculación
Una declaración de enlace estructurado primero introduce una variable con nombre único (aquí denotada por e ) para contener el valor del inicializador, de la siguiente manera:
-
Si
expression
tiene tipo array
cv1
Ay no está presente ningún ref-qualifier , defina e como attr (opcional) specifiersA e;, donde specifiers es una secuencia de los especificadores en decl-specifier-seq excluyendo auto .
-
Entonces cada elemento de
e
se inicializa desde el elemento correspondiente de
expression
según lo especificado por la forma del
initializer
:
- Para la sintaxis de inicializador (1) , los elementos son copy-initialized .
- Para las sintaxis de inicializador (2,3) , los elementos son direct-initialized .
-
De lo contrario, defina
e
como
attr
(opcional)
decl-specifier-seq
ref-qualifier
(opcional)
einitializer ;.
Usamos
E
para denotar el tipo de la expresión de identificador
e
(es decir,
E
es el equivalente de
std::
remove_reference_t
<
decltype
(
(
e
)
)
>
).
Un
tamaño de enlace estructurado
de
E
es el número de enlaces estructurados que deben introducirse mediante la declaración de enlace estructurado.
|
El número de identificadores en
sb-identifier-list
debe ser igual al tamaño de enlace estructurado de
|
(until C++26) |
|
Dado el número de identificadores en
sb-identifier-list
como
N
y el tamaño de enlace estructurado de
|
(since C++26) |
struct C { int x, y, z; }; template<class T> void now_i_know_my() { auto [a, b, c] = C(); // CORRECTO: a, b, c se refieren a x, y, z, respectivamente auto [d, ...e] = C(); // CORRECTO: d se refiere a x; ...e se refiere a y y z auto [...f, g] = C(); // CORRECTO: ...f se refiere a x e y; g se refiere a z auto [h, i, j, ...k] = C(); // CORRECTO: el paquete k está vacío auto [l, m, n, o, ...p] = C(); // error: el tamaño del enlace estructurado es demasiado pequeño }
Una declaración de enlace estructurado realiza el enlace de una de tres maneras posibles, dependiendo de
E
:
-
Caso 1: Si
Ees un tipo array, entonces los nombres se vinculan a los elementos del array. -
Caso 2: Si
Ees un tipo clase no-union y std:: tuple_size < E > es un tipo completo con un miembro llamadovalue(independientemente del tipo o accesibilidad de dicho miembro), entonces se utiliza el protocolo de vinculación "tipo-tupla". -
Caso 3: Si
Ees un tipo clase no-union pero std:: tuple_size < E > no es un tipo completo, entonces los nombres se vinculan a los miembros de datos accesibles deE.
Cada uno de los tres casos se describe con más detalle a continuación.
Cada enlace estructurado tiene un
tipo referenciado
, definido en la descripción a continuación. Este tipo es el tipo devuelto por
decltype
cuando se aplica a un enlace estructurado sin paréntesis.
Caso 1: vinculando un array
Cada enlace estructurado en la
lista-de-identificadores-sb
se convierte en el nombre de un lvalue que hace referencia al elemento correspondiente del array. El tamaño del enlace estructurado de
E
es igual al número de elementos del array.
El
tipo referenciado
para cada enlace estructurado es el tipo de elemento del array. Nótese que si el tipo de array
E
está calificado cv, también lo está su tipo de elemento.
int a[2] = {1, 2}; auto [x, y] = a; // crea e[2], copia a en e, // luego x se refiere a e[0], y se refiere a e[1] auto& [xr, yr] = a; // xr se refiere a a[0], yr se refiere a a[1]
Caso 2: vinculación de un tipo que implementa las operaciones de tupla
La expresión
std::
tuple_size
<
E
>
::
value
debe ser una
expresión constante integral
bien formada, y el tamaño del enlace estructurado de
E
es igual a
std::
tuple_size
<
E
>
::
value
.
Para cada enlace estructurado, se introduce una variable cuyo tipo es "referencia a std:: tuple_element < I, E > :: type ": referencia lvalue si su inicializador correspondiente es un lvalue, referencia rvalue en caso contrario. El inicializador para la I -ésima variable es
-
e.
get
<
I
>
(
)
, si la búsqueda del identificador
geten el ámbito deEmediante búsqueda de acceso a miembro de clase encuentra al menos una declaración que es una plantilla de función cuyo primer parámetro de plantilla es un parámetro constante - De lo contrario, get < I > ( e ) , donde get se busca únicamente mediante búsqueda dependiente de argumento , ignorando la búsqueda no-ADL.
En estas expresiones de inicialización,
e
es un lvalue si el tipo de la entidad
e
es una referencia lvalue (esto solo ocurre si el
ref-qualifier
es
&
o si es
&&
y la expresión de inicialización es un lvalue) y un xvalue en caso contrario (esto efectivamente realiza una especie de perfect forwarding),
I
es un prvalue de tipo
std::size_t
, y
<
I
>
siempre se interpreta como una lista de parámetros de plantilla.
La variable tiene la misma storage duration que e .
La vinculación estructurada se convierte entonces en el nombre de un lvalue que se refiere al objeto vinculado a dicha variable.
El tipo referenciado para el I -ésimo structured binding es 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; // usando Tpl = const std::tuple<float&, char&&, int>; // a nombra un enlace estructurado que se refiere a x (inicializado desde get<0>(tpl)) // decltype(a) es std::tuple_element<0, Tpl>::type, es decir float& // b nombra un enlace estructurado que se refiere a y (inicializado desde get<1>(tpl)) // decltype(b) es std::tuple_element<1, Tpl>::type, es decir char&& // c nombra un enlace estructurado que se refiere al tercer componente de tpl, get<2>(tpl) // decltype(c) es std::tuple_element<2, Tpl>::type, es decir const int
Caso 3: vinculación a miembros de datos
Cada miembro de datos no estático de
E
debe ser un miembro directo de
E
o de la misma clase base de
E
, y debe estar bien formado en el contexto del enlace estructurado cuando se nombra como
e.
name
.
E
no puede tener un miembro de unión anónima. El tamaño del enlace estructurado de
E
es igual al número de miembros de datos no estáticos.
Cada enlace estructurado en
sb-identifier-list
se convierte en el nombre de un lvalue que hace referencia al siguiente miembro de
e
en orden de declaración (los campos de bits están soportados); el tipo del lvalue es el de
e.
mI
, donde
mI
hace referencia al
I
ésimo miembro.
El
tipo referenciado
del
I
-ésimo enlace estructurado es el tipo de
e.
mI
si no es un tipo referencia, o el tipo declarado de
mI
en caso contrario.
#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 es un lvalue int que identifica el campo de bits de 2 bits // y es un lvalue double const volatile std::cout << x << ' ' << y << '\n'; // 1 2.3 x = -2; // OK // y = -2.; // Error: y está calificado como const std::cout << x << ' ' << y << '\n'; // -2 2.3 }
Orden de inicialización
Sea valI el objeto o referencia denominado por el I ésimo enlace estructurado en sb-identifier-list :
- La inicialización de e está secuenciada antes que la inicialización de cualquier valI .
- La inicialización de cada valI está secuenciada antes que la inicialización de cualquier valJ donde I es menor que J .
Notas
|
Los enlaces estructurados no pueden ser restringidos : template<class T> concept C = true; C auto [x, y] = std::pair{1, 2}; // error: constrained |
(desde C++20) |
La búsqueda del miembro
get
ignora la accesibilidad como es habitual y también ignora el tipo exacto del parámetro de plantilla constante. Un miembro privado
template
<
char
*
>
void
get
(
)
;
hará que se utilice la interpretación del miembro, aunque sea incorrecta.
La parte de la declaración que precede a
[
se aplica a la variable oculta
e
, no a los enlaces estructurados introducidos:
La interpretación similar a una tupla siempre se utiliza si
std::
tuple_size
<
E
>
es un tipo completo con un miembro llamado
value
, incluso si eso causaría que el programa esté mal formado:
struct A { int x; }; namespace std { template<> struct tuple_size<::A> { void value(); }; } auto [x] = A{}; // error; la interpretación de "miembro de datos" no se considera.
Las reglas habituales para el enlace de referencias a temporales (incluyendo la extensión de vida) se aplican si un ref-qualifier está presente y la expresión es un prvalue. En esos casos la variable oculta e es una referencia que se enlaza a la variable temporal materializada a partir de la expresión prvalue, extendiendo su tiempo de vida. Como es habitual, el enlace fallará si e es una referencia lvalue no constante:
int a = 1; const auto& [x] = std::make_tuple(a); // CORRECTO, no es un puntero colgante auto& [y] = std::make_tuple(a); // error, no se puede vincular auto& a rvalue std::tuple auto&& [z] = std::make_tuple(a); // también CORRECTO
decltype ( x ) , donde x denota un enlace estructurado, nombra el tipo referenciado de ese enlace estructurado. En el caso tipo-tupla, este es el tipo devuelto por std::tuple_element , que puede no ser una referencia aunque en este caso siempre se introduce una referencia oculta. Esto emula efectivamente el comportamiento de enlazar a una estructura cuyos miembros de datos no estáticos tienen los tipos devueltos por std::tuple_element , siendo la referencia del enlace en sí un mero detalle de implementación.
std::tuple<int, int&> f(); auto [x, y] = f(); // decltype(x) es int // decltype(y) es int& const auto [z, w] = f(); // decltype(z) es const int // decltype(w) es int&
|
Los enlaces estructurados no pueden ser capturados por expresiones lambda : #include <cassert> int main() { struct S { int p{6}, q{7}; }; const auto& [b, d] = S{}; auto l = [b, d] { return b * d; }; // válido desde C++20 assert(l() == 42); } |
(hasta C++20) |
|
Se permite que el tamaño de un enlace estructurado sea 0 siempre que la lista-de-identificadores-sb contenga exactamente un identificador que solo pueda introducir un paquete de enlace estructurado vacío. auto return_empty() -> std::tuple<>; template <class> void test_empty() { auto [] = return_empty(); // error auto [...args] = return_empty(); // OK, args es un paquete vacío auto [one, ...rest] = return_empty(); // error, el tamaño del enlace estructurado es demasiado pequeño } |
(desde C++26) |
| Macro de prueba de características | Valor | Std | Característica |
|---|---|---|---|
__cpp_structured_bindings
|
201606L
|
(C++17) | Enlaces estructurados |
202403L
|
(C++26) | Enlaces estructurados con atributos | |
202406L
|
(C++26) | Declaración de enlace estructurado como condición | |
202411L
|
(C++26) | Los enlaces estructurados pueden introducir un paquete |
Palabras clave
Ejemplo
#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 << "La inserción es exitosa. El valor es " << std::quoted(*iter) << ".\n"; else std::cout << "El valor " << std::quoted(*iter) << " ya existe en el conjunto.\n"; } struct BitFields { // C++20: inicializador predeterminado de miembro para campos de bits 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'; } }
Salida:
La inserción es exitosa. El valor es "Hello". El valor "Hello" ya existe en el conjunto. 1 2 3 4 4 3 2 1 1 2 3 4 4 3 2 1
Informes de defectos
Los siguientes informes de defectos que modifican el comportamiento se aplicaron retroactivamente a los estándares publicados anteriormente de C++.
| DR | Aplicado a | Comportamiento publicado | Comportamiento correcto |
|---|---|---|---|
| CWG 2285 | C++17 | expression podría referirse a los nombres de identifier-list |
la declaración es
incorrecta en este caso |
| CWG 2312 | C++17 | el significado de mutable se perdía en el caso 3 | su significado se mantiene |
| CWG 2313 | C++17 | en el caso 2, las variables de enlace estructurado podían redeclararse | no pueden redeclararse |
| CWG 2339 | C++17 | en el caso 2, faltaba la definición de I | se añadió la definición |
|
CWG 2341
( P1091R3 ) |
C++17 |
los enlaces estructurados no podían
declararse con duración de almacenamiento estático |
permitido |
| CWG 2386 | C++17 |
el protocolo de enlace "tipo-tupla" se usaba
siempre que std:: tuple_size < E > fuera un tipo completo |
se usa solo cuando
std::
tuple_size
<
E
>
tiene un miembro
value
|
| CWG 2506 | C++17 |
si
expression
es de tipo array con calificación cv,
la calificación cv se transfería a
E
|
descarta esa calificación cv |
| CWG 2635 | C++20 | los enlaces estructurados podían estar restringidos | prohibido |
| CWG 2867 | C++17 | el orden de inicialización no estaba claro | se aclaró |
| P0961R1 | C++17 |
en el caso 2, se usaba el miembro
get
si la búsqueda encontraba cualquier tipo de
get
|
solo si la búsqueda encuentra una plantilla
de función con un parámetro constante |
| P0969R0 | C++17 | en el caso 3, se requería que los miembros fueran públicos |
solo se requiere que sean accesibles
en el contexto de la declaración |
Referencias
- Estándar C++23 (ISO/IEC 14882:2024):
-
- 9.6 Declaraciones de enlace estructurado [dcl.struct.bind] (p: 228-229)
- Estándar C++20 (ISO/IEC 14882:2020):
-
- 9.6 Declaraciones de enlace estructurado [dcl.struct.bind] (p: 219-220)
- Estándar C++17 (ISO/IEC 14882:2017):
-
- 11.5 Declaraciones de enlace estructurado [dcl.struct.bind] (p: 219-220)
Véase también
|
(C++11)
|
crea un
tuple
de referencias a lvalue o desempaqueta un tuple en objetos individuales
(plantilla de función) |