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 quei
: cuando soy unaint const
, ¿por quéri
no esconst
?
¿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 .
std::remove_reference
usar
std::remove_reference
para obtener el valor que está buscando.
std::cout << std::is_const<std::remove_reference<decltype(ri)>::type>::value << std::endl;
Para más información, vea esta publicación .