Espacios de nombres
Variantes
Acciones

Declaraciones de vínculos estructurados (desde C++17)

De cppreference.com
< cpp‎ | language
 
 
 
 

Vincula los nombres especificados a subobjetos o elementos del inicializador.

Al igual que una referencia, un vínculo estructurado es una alias a un objeto existente. A diferencia de una referencia, el tipo del vínculo estructurado no tiene que ser un tipo de referencia.

atrib(opcional) cv-auto operador-ref(opcional) [ lista-de-identificadores ] = expresión ; (1)
atrib(opcional) cv-auto operador-ref(opcional) [ lista-de-identificadores ] { expresión } ; (2)
atrib(opcional) cv-auto operador-ref(opcional) [ lista-de-identificadores ] ( expresión ) ; (3)
atrib - Secuencia de cualquier número de atributos.
cv-auto - Especificador de tipo posiblemente calificado-cv auto , también puede incluir un especificador-de-clase-de-almacenamiento static o thread_local (desde C++20)
operador-ref - Ya sea & o &&
lista-de-identificadores - Lista de identificadores separados por coma introducidos por esta declaración.
expresión - una expresión que no tiene el operador coma en el más alto nivel (gramaticalmente, una expresión-de-asignación), y tiene bien un tipo de clase array o no-unión. Si la expresión se refiere a cualquiera de los nombres de la to lista-de-identificadores, la declaración está mal formada.

Una declaración de vínculo estructurado introduce todos los identificadores en la lista-de-identificadores como nombres en el ámbito en que se encuentra y los vincula a los subobjetos o elementos del objeto denotado por la expresión. A los vínculos introducidos se les llama vínculos estructurados.

Una declaración de vínculo estructurado primero introduce una variable anónima con un nombre único (aquí denotada por e) para mantener el valor del inicializador de la manera siguiente:

  • Si la expresión tiene un tipo de array A y no está presente un operador-ref, entonces e tiene tipo cv A, dondecv son los calificadores-cv en la secuencia cv-auto, y cada elemento de e es inicializado por copia (para (1)) o inicializado directamente (para (2,3)) desde el elemento correspondiente de la expresión.
  • De otra manera, e se define como si se usara su nombre en lugar de [ lista-de-identificadores ] en la declaración.

Usamos E para denotar el tipo de la expresión e. En otras palabras, E equivale a std::remove_reference_t<decltype((e))>.

Una declaración de vínculo estructurado realiza entonces la vinculación de tres maneras posibles, dependiendo de E:

  • Caso 1: Si E es un tipo de array, entonces los nombres se vinculan a los elementos del array.
  • Caso 2: Si E es de un tipo de clase no-unión y std::tuple_size<E> es un tipo completo con un miembro llamado value (independientemente del tipo de accesibilidad de tal miembro), entonces se usa el protocolo de vinculación "similar a una tupla".
  • Caso 3: Si E es un tipo de clase no-unión pero std::tuple_size<E> no es un tipo completo, entonces los nombres se vinculan a los datos miembro accesibles de E.

Cada uno de los tres casos se describe en más detalle a continuación.

Cada vínculo estructurado tiene un tipo referido, definido en la descripción que sigue. Este tipo es el tipo devuelto por decltype cuando se aplica a un vínculo estructurado sin paréntesis.

Contenido

[editar] Caso 1: Vincular un array

Cada identificador en la lista-de-identificadores se vuelve el nombre de un lvalue que se refiere al elemento correspondiente del array. El número de identificadores tiene que ser igual al número de elementos del array.

El tipo referido para cada identificador es el tipo del elemento del array. Observa 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[2], copia a en e, entonces x se refiere a e[0], e y se refiere a e[1]
auto& [xr, yr] = a; // <code>xr</code> se refiere a a[0], yr se refiere a a[1]

[editar] Caso 2: Vincular un tipo similar a una tupla

La expresión std::tuple_size<E>::value tiene que ser una expresión constante entera bien formada, y el número de identificadores tiene que ser igual a std::tuple_size<E>::value.

Para cada identificador, se introduce una variable cuyo tipo es "referencia a std::tuple_element<i, E>::type": una referencia lvalue si su inicializador correspondiente es un lvalue, de otra forma, una referencia rvalue. El inicializador para la i-ésima variable es:

  • e.get<i>(), si la búsqueda del identificador get en el ámbito de E mediante la 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 de no tipo.
  • De otra forma, get<i>(e), donde get se busca mediante la búsqueda dependiente de argumento solamente, ignorando búsquedas que no son dependientes de argumento.

En estas expresiones de inicializador, e es un lvalue si el tipo de la entidad e es una referencia lvalue (esto solamente sucede si el operador-ref es & o si es && y la expresión de inicializador es un lvalue) y un xvalue de otra forma (esto efectivamente realiza una clase de reenvío perfecto), i es un std::size_t prvalue, e <i> siempre se interpreta como una lista de parámetros de plantilla.

La variable tiene la misma duración de almacenamiento que e.

Entonces el identificador se vuelve el nombre de un lvalue que se refiere al objeto vinculado a dicha variable.

El tipo referenciado por el identificador i-ésimo 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;
// a da nombre a un vínculo estructurado que se refiere a x; decltype(a) es float&
// b da nombre a un vínculo estructurado que se refiere a y; decltype(b) es char&&
// c da nombre a un vínculo estructurado que se refiere al tercer elemento de tpl; decltype(c) es const int

[editar] Caso 3: Vincular datos miembro

Cada dato miembro no estático de E tiene que ser un miembro directo de E o la misma clase base de E, y tiene que estar bien formado en el contexto del vínculo estructurado cuando se le llame e.nombre. E no puede tener un miembro de unión anónima. El número de identificadores tiene que ser igual al número de datos miembro no estáticos.

Cada identificador en la lista-de-identificadores se vuelve el nombre de un lvalue que se refiere al próximo miembro de e en el orden de declaración (se soportan los campos de bits); el tipo del lvalue es cv T_i, donde cv son los calificadores-cv de E y T_i es el tipo declarado del i-ésimo miembro.

El tipo referenciado del identificador i-ésimo es cv T_i.

struct S {
    int x1 : 2;
    volatile double y1;
};
S f();
 
const auto [x, y] = f(); // x es un lvalue const int que identifica el campo de bits de 2 bits
                         // y es un lvalue const volatile double

[editar] Notas

La búsqueda del miembro get, como siempre, ignora accesibilidad y también ignora el tipo exacto del parámetro de plantilla de no tipo. Un miembro privado template<char*> void get(); ocasionará que se utilice la interpretación del miembro, aun cuando esté mal formado.

La porción de la declaración que precede a [ se aplica a la variable oculta e, no a los identificadores introducidos.

int a = 1, b = 2;
const auto& [x, y] = std::tie(a, b); // x e y son de tipo int&
auto [z, w] = std::tie(a, b);        // z y w are todavía de tipo int&
assert(&z == &a);                    // pasa

La interpretación de "similar a una tupla" siempre se usa si std::tuple_size<E> es un tipo completo, inclusive si eso ocasionaría que el programa esté mal formado:

struct A { int x; };
namespace std {
    template<> struct tuple_size<::A> {};
}
 
auto [x] = A{}; // ERROR: no se considera la interpretación de "dato miembro".

Las reglas habituales para vinculación de referencias a objetos temporales (incluyendo extensión de su duración) se aplican si un operador-ref está presente y la expresión es un prvalue. En esos casos, la variable oculta e es una referencia que se vincula a la variable temporal materializada de la expresión prvalue, extendiendo su duración. Como es habitual, la vinculación fallará si e es una referencia no constante a un lvalue.

int a = 1;
const auto& [x] = std::make_tuple(a); // de acuerdo, no está pendiente
auto&       [y] = std::make_tuple(a); // ERROR: no se puede vincular auto& a un rvalue std::tuple
auto&&      [z] = std::make_tuple(a); // tambien de acuerdo

decltype(x), donde x denota un vínculo estructurado, da nombre al tipo referenciado de ese vínculo estructurado. En el caso similar a una tupla, este es el tipo devuelto por std::tuple_element, que no puede ser una referencia aun cuando el vínculo estructurado mismo siempre se comporte como una referencia en este caso. Esto efectivamente emula el comportamiento de vincular a una estructura cuyos datos miembro no estáticos tienen los tipos devueltos por tuple_element, siendo la manera en que el vínculo mismo se referencia un simple detalle de la 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&

[editar] Ejemplo

#include <set>
#include <string>
#include <iomanip>
#include <iostream>
 
int main() {
    std::set<std::string> miconjunto;
    if (auto [iter, exito] = miconjunto.insert("Hola"); exito) 
        std::cout << "Insercion tuvo exito. El valor es " << std::quoted(*iter) << '\n';
    else
        std::cout << "El valor " << std::quoted(*iter) << " ya existe en el conjunto\n";
}

Salida:

Insercion tuvo exito. El valor es "Hola"

[editar] Informes de defectos

Los siguientes informes de defectos de cambio de comportamiento se aplicaron de manera retroactiva a los estándares de C++ publicados anteriormente.

ID Aplicado a Comportamiento según lo publicado Comportamiento correcto
P0961R1 C++17 En el caso similar a una tupla, se usa el miembro get si la búsqueda
encuentra una función get de cualquier tipo.
Solamente si la búsqueda encuentra una plantilla de
función con un parámetro de no-tipo.
P0969R0 C++17 En el caso de vinculación de datos
miembro, se requiere que los datos miembro sean públicos.
Solamente se requiere que sean accesibles en el
contexto de la declaración.
CWG 2386 C++17 El protocolo "similar a una tupla" se usa siempre y cuando tuple_size<E>
sea un tipo completo.
Solamente se usa cuando tuple_size<E> tiene un valor
miembro.

[editar] Véase también

Crea una tupla de referencias lvalue o desempaca una tupla en objetos individuales.
(plantilla de función) [editar]