whats news new microsoft features docs compute c++ reference const language-lawyer decltype

c++ - news - new features c#



¿Por qué las referencias no son "constantes" en C++? (6)

¿Por qué "ri" no es "const"?

std::is_const comprueba si el tipo está calificado o no const.

Si T es un tipo calificado const (es decir, const o const volátil), proporciona al miembro un valor constante igual a verdadero. Para cualquier otro tipo, el valor es falso.

Pero la referencia no puede ser constante. Referencias [dcl.ref] / 1

Las referencias calificadas para Cv están mal formadas, excepto cuando los calificadores cv se introducen mediante el uso de un typedef-name ([dcl.typedef], [temp.param]) o decltype-specifier ([dcl.type.simple]) , en cuyo caso se ignoran los calificadores cv.

Entonces is_const<decltype(ri)>::value devolverá false porque ri (la referencia) no es un tipo calificado const. Como dijiste, no podemos volver a vincular una referencia después de la inicialización, lo que implica que la referencia siempre es "const", por otro lado, la referencia const const o la referencia sin const en realidad podría no tener sentido.

Sabemos que una "variable constante" indica que una vez asignada, no puede cambiar la variable, así:

int const i = 1; i = 2;

El programa anterior no se compilará; gcc solicita un error:

assignment of read-only variable ''i''

No hay problema, puedo entenderlo, pero el siguiente ejemplo está más allá de mi comprensión:

#include<iostream> using namespace std; int main() { boolalpha(cout); int const i = 1; cout << is_const<decltype(i)>::value << endl; int const &ri = i; cout << is_const<decltype(ri)>::value << endl; return 0; }

Sale

true false

Extraño. Sabemos que una vez que una referencia está vinculada a un nombre / variable, no podemos cambiar este enlace, cambiamos su objeto vinculado. Así que supongo que el tipo de ri debería ser el mismo que i : cuando soy un int const , ¿por qué ri no es const ?


¿Por qué las macros no son const ? Funciones? Literales? Los nombres de los tipos?

const cosas const son solo un subconjunto de cosas inmutables.

Dado que los tipos de referencia son solo eso, tipos, puede tener sentido requerir el calificador const en todos ellos para la simetría con otros tipos (particularmente con los tipos de puntero), pero esto se volvería muy tedioso muy rápidamente.

Si C ++ tuviera objetos inmutables por defecto, requiriendo la palabra clave mutable en cualquier cosa que no quisiera ser const , entonces esto habría sido fácil: simplemente no permita que los programadores agreguen mutable a tipos de referencia.

Tal como están las cosas, son inmutables sin calificación.

Y, dado que no están calificados const , probablemente sería más confuso que is_const en un tipo de referencia arroje verdadero.

Considero que esto es un compromiso razonable, especialmente porque la inmutabilidad se impone de todos modos por el simple hecho de que no existe sintaxis para mutar una referencia.


Esta es una peculiaridad / característica en C ++. Aunque no pensamos en las referencias como tipos, de hecho, "se sientan" en el sistema de tipos. Aunque esto parece incómodo (dado que cuando se usan referencias, la semántica de referencia ocurre automáticamente y la referencia "se sale del camino"), hay algunas razones defendibles por las cuales las referencias se modelan en el sistema de tipos en lugar de como un atributo separado fuera de tipo.

En primer lugar, consideremos que no todos los atributos de un nombre declarado deben estar en el sistema de tipos. Desde el lenguaje C, tenemos "clase de almacenamiento" y "enlace". Se puede introducir un nombre como extern const int ri , donde extern indica la clase de almacenamiento estático y la presencia de enlaces. El tipo es simplemente const int .

C ++ obviamente abarca la noción de que las expresiones tienen atributos que están fuera del sistema de tipos. El lenguaje ahora tiene un concepto de "clase de valor", que es un intento de organizar el creciente número de atributos que no son de tipo que una expresión puede exhibir.

Sin embargo, las referencias son tipos. ¿Por qué?

Solía ​​explicarse en los tutoriales de C ++ que una declaración como const int &ri introdujo ri como teniendo tipo const int , pero semántica de referencia. Esa semántica de referencia no era un tipo; era simplemente un tipo de atributo que indica una relación inusual entre el nombre y la ubicación de almacenamiento. Además, el hecho de que las referencias no son tipos se utilizó para racionalizar por qué no puede construir tipos basados ​​en referencias, aunque la sintaxis de construcción de tipos lo permita. Por ejemplo, las matrices o punteros a referencias no son posibles: const int &ari[5] y const int &*pri .

Pero, de hecho, las referencias son tipos y decltype(ri) recupera algún nodo de tipo de referencia que no está calificado. Debe descender más allá de este nodo en el árbol de tipos para llegar al tipo subyacente con remove_reference .

Cuando usa ri , la referencia se resuelve de forma transparente, de modo que ri "se ve y se siente como i " y se le puede llamar un "alias". Sin embargo, en el sistema de tipos, ri tiene un tipo que es " referencia a const int ".

¿Por qué son los tipos de referencias?

Tenga en cuenta que si las referencias no fueran tipos, se consideraría que estas funciones tienen el mismo tipo:

void foo(int); void foo(int &);

Eso simplemente no puede ser por razones que son bastante evidentes. Si tuvieran el mismo tipo, eso significa que cualquiera de las declaraciones sería adecuada para cualquier definición, por lo que se debería sospechar que cada función (int) toma una referencia.

Del mismo modo, si las referencias no fueran tipos, entonces estas dos declaraciones de clase serían equivalentes:

class foo { int m; }; class foo { int &m; };

Sería correcto que una unidad de traducción use una declaración, y otra unidad de traducción en el mismo programa use la otra declaración.

El hecho es que una referencia implica una diferencia en la implementación y es imposible separarla del tipo, porque el tipo en C ++ tiene que ver con la implementación de una entidad: su "diseño" en bits, por así decirlo. Si dos funciones tienen el mismo tipo, se pueden invocar con las mismas convenciones de llamadas binarias: el ABI es el mismo. Si dos estructuras o clases tienen el mismo tipo, su diseño es el mismo y la semántica de acceso a todos los miembros. La presencia de referencias cambia estos aspectos de los tipos, por lo que es una decisión de diseño directa incorporarlos al sistema de tipos. (Sin embargo, tenga en cuenta un argumento en contra aquí: un miembro de estructura / clase puede ser static , lo que también cambia la representación; ¡pero eso no es tipo!)

Por lo tanto, las referencias están en el sistema de tipos como "ciudadanos de segunda clase" (no muy diferente de las funciones y matrices en ISO C). Hay ciertas cosas que no podemos "hacer" con referencias, como declarar punteros a referencias o matrices de ellos. Pero eso no significa que no sean tipos. Simplemente no son tipos de una manera que tenga sentido.

No todas estas restricciones de segunda clase son esenciales. Dado que existen estructuras de referencias, ¡podría haber matrices de referencias! P.ej

// fantasy syntax int x = 0, y = 0; int &ar[2] = { x, y }; // ar[0] is now an alias for x: could be useful!

Esto simplemente no está implementado en C ++, eso es todo. Sin embargo, los punteros a las referencias no tienen ningún sentido, porque un puntero levantado de una referencia solo va al objeto referenciado. La razón probable por la que no hay matrices de referencias es que las personas de C ++ consideran que las matrices son un tipo de característica de bajo nivel heredada de C que se rompe de muchas maneras que son irreparables, y no quieren tocar matrices como base para cualquier cosa nueva. Sin embargo, la existencia de matrices de referencias sería un claro ejemplo de cómo las referencias tienen que ser tipos.

Tipos calificables no const : ¡también se encuentran en ISO C90!

Algunas respuestas indican que las referencias no toman un calificador const . Es más bien una pista falsa, porque la declaración const int &ri = i ni siquiera intenta hacer una referencia calificada const : es una referencia a un tipo calificado const (que en sí mismo no es const ). Al igual que const in *ri declara un puntero a algo const , pero ese puntero en sí mismo no es const .

Dicho esto, es cierto que las referencias no pueden llevar el calificador const sí mismas.

Sin embargo, esto no es tan extraño. Incluso en el lenguaje ISO C 90, no todos los tipos pueden ser const . A saber, las matrices no pueden ser.

En primer lugar, la sintaxis no existe para declarar una matriz const: int a const [42] es errónea.

Sin embargo, lo que intenta hacer la declaración anterior se puede expresar a través de un typedef intermedio:

typedef int array_t[42]; const array_t a;

Pero esto no hace lo que parece. En esta declaración, no es a que se califica const , ¡sino los elementos! Es decir, a[0] es una const int , pero a es solo "matriz de int". En consecuencia, esto no requiere un diagnóstico:

int *p = a; /* surprise! */

Esto hace:

a[0] = 1;

Nuevamente, esto subraya la idea de que las referencias son, en cierto sentido, "segunda clase" en el sistema de tipos, como las matrices.

Observe cómo la analogía es aún más profunda, ya que las matrices también tienen un "comportamiento de conversión invisible", como referencias. Sin que el programador tenga que usar ningún operador explícito, el identificador a se convierte automáticamente en un puntero int * , como si se hubiera usado la expresión &a[0] . Esto es análogo a cómo una referencia ri , cuando la usamos como expresión primaria, denota mágicamente el objeto i al que está vinculada. Es solo otra "decadencia" como la "decadencia de matriz a puntero".

Y al igual que no debemos confundirnos por la descomposición de "matriz a puntero" y pensar erróneamente que "las matrices son solo punteros en C y C ++", tampoco debemos pensar que las referencias son solo alias que no tienen ningún tipo de ellos.

Cuando decltype(ri) suprime la conversión habitual de la referencia a su objeto de referencia, esto no es tan diferente del decltype(ri) suprimir la conversión de matriz a puntero y operar en el tipo de matriz para calcular su tamaño.


Esto puede parecer contrario a la intuición, pero creo que la forma de entender esto es darse cuenta de que, en ciertos aspectos, las referencias se tratan sintácticamente como punteros .

Esto parece lógico para un puntero :

int main() { boolalpha(cout); int const i = 1; cout << is_const<decltype(i)>::value << endl; int const* ri = &i; cout << is_const<decltype(ri)>::value << endl; }

Salida:

true false

Esto es lógico porque sabemos que no es el objeto puntero lo que es constante (se puede hacer que apunte a otra parte), es el objeto al que se apunta.

Por lo tanto, vemos correctamente la constidad del puntero en sí mismo devuelto como false .

Si queremos que el puntero sea const , tenemos que decir:

int main() { boolalpha(cout); int const i = 1; cout << is_const<decltype(i)>::value << endl; int const* const ri = &i; cout << is_const<decltype(ri)>::value << endl; }

Salida:

true true

Y creo que vemos una analogía sintáctica con la referencia .

Sin embargo, las referencias son semánticamente diferentes a los punteros, especialmente en un aspecto crucial, no se nos permite volver a vincular una referencia a otro objeto una vez vinculado.

Entonces, aunque las referencias comparten la misma sintaxis que los punteros, las reglas son diferentes y, por lo tanto, el lenguaje nos impide declarar la referencia en sí misma de esta manera:

int main() { boolalpha(cout); int const i = 1; cout << is_const<decltype(i)>::value << endl; int const& const ri = i; // COMPILE TIME ERROR! cout << is_const<decltype(ri)>::value << endl; }

Supongo que no se nos permite hacer esto porque no parece ser necesario cuando las reglas del lenguaje evitan que la referencia se rebote de la misma manera que un puntero podría hacerlo (si no se declara const ).

Entonces para responder la pregunta:

P) ¿Por qué "referencia" no es una "constante" en C ++?

En su ejemplo, la sintaxis hace que la cosa a la que se hace referencia sea const la misma manera que si estuviera declarando un puntero .

Bien o mal, no se nos permite hacer la referencia en sí misma, pero si lo hiciéramos, se vería así:

int const& const ri = i; // not allowed

P) sabemos que una vez que una referencia está vinculada a un nombre / variable, no podemos cambiar este enlace, cambiamos su objeto vinculado. Así que supongo que el tipo de ri debería ser el mismo que i : cuando soy una int const , ¿por qué ri no es const ?

¿Por qué el decltype() no se transfiere al objeto al que está vinculada la referencia ?

Supongo que esto es para la equivalencia semántica con punteros y tal vez también la función de decltype() (tipo declarado) es mirar hacia atrás a lo que se declaró antes de que tuviera lugar el enlace.


const X & x ”significa que x alias un objeto X, pero no puede cambiar ese objeto X a través de x.

Y vea std::is_const .