Sabemos que una "variable constante" indica que una vez asignada, no se puede cambiar la variable, así:
int const i = 1;
i = 2;
El programa anterior no se podrá compilar; gcc muestra 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;
}
Se produce
true
false
Extraño. Sabemos que una vez que una referencia está vinculada a un nombre / variable, no podemos cambiar esta vinculación, cambiamos su objeto vinculado. Así que supongo que el tipo de ri
debería ser el mismo que i
: ¿cuándo i
es un int const
, por qué ri
no const
?
boolalpha(cout)
es muy inusual. Podría hacerlo en sustd::cout << boolalpha
lugar.ri
ser un "alias" indistinguible dei
.i
también es una referencia pero por razones históricas no lo declaras como tal de forma explícita. Por tanto,i
es referencia que se refiere a un almacenamiento yri
es una referencia que se refiere al mismo almacenamiento. Pero no hay diferencia en la naturaleza entrei
yri
. Como no puede cambiar la vinculación de una referencia, no es necesario calificarla comoconst
. Y permítanme afirmar que el comentario de @Kaz es mucho mejor que la respuesta validada (nunca explique las referencias usando punteros, una referencia es un nombre, un ptr es una variable).is_const
volver tambiéntrue
en este caso. En mi opinión, este es un buen ejemplo de por quéconst
es fundamentalmente al revés; un atributo "mutable" (a la Rustmut
) parece que sería más consistente.is_const<int const &>::value
falso?" o similar; Estoy luchando por ver algún significado a la pregunta que no sea sobre el comportamiento de los rasgos de tipoRespuestas:
Esto puede parecer contrario a la intuición, pero creo que la forma de entenderlo 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 el que es constante (se puede hacer que apunte a otra parte), es el objeto al que se apunta.
Entonces vemos correctamente que la constness del puntero se devuelve como
false
.Si queremos hacer el propio puntero
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 entonces 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í de
const
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 impiden que la referencia rebote de la misma manera que lo haría un puntero (si no se declara
const
).Entonces, para responder a la pregunta:
En su ejemplo, la sintaxis hace que se haga referencia a la cosa de
const
la misma manera que lo haría si estuviera declarando un puntero .Correcta o incorrectamente, no podemos hacer la referencia en sí,
const
pero si lo hiciéramos, se vería así:int const& const ri = i; // not allowed
¿Por qué
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 en lo que se declaró antes de que tuviera lugar el enlace.fuente
decltype
y descubrió que no es así.const
, porstd::is_const
lo tanto debe regresarfalse
. En su lugar, podrían haber usado una redacción que significara que debe regresartrue
, pero no lo hicieron. ¡Eso es! Todo esto sobre punteros, "supongo", "supongo", etc., no proporciona ninguna aclaración real.decltype
no es una operación en tiempo de ejecución, por lo que la idea de "en tiempo de ejecución, las referencias se comportan como punteros", sea correcta o no, no se aplica realmente.std::is_const
comprueba si el tipo está calificado const o no.Pero la referencia no puede calificarse const. Referencias [dcl.ref] / 1
Por
is_const<decltype(ri)>::value
lo tanto , volveráfalse
porqueri
(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-calificada o la referencia const-no calificada podrían no tener sentido en realidad.fuente
is_const
regresatrue
? Esa respuesta intenta establecer una analogía de cómo los punteros se pueden volver a cerrar opcionalmente, mientras que las referencias no lo son, y al hacerlo, conduce a la autocontradicción por la misma razón. No estoy seguro de que haya una explicación real de cualquier manera, aparte de una decisión algo arbitraria de quienes escriben el Estándar, y a veces eso es lo mejor que podemos esperar. De ahí esta respuesta.decltype
es una función y, por lo tanto, trabaja directamente en la referencia en sí y no en el objeto referido. (Esto es quizás más relevante para la respuesta de "las referencias son básicamente indicadores", pero sigo pensando que es parte de lo que hace que este ejemplo sea confuso y, por lo tanto, vale la pena mencionarlo aquí).decltype(name)
actúa de manera diferente a un generaldecltype(expr)
. Por ejemplo,decltype(i)
es el tipo declarado dei
which isconst int
, whiledecltype((i))
would beint const &
.Necesita 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 obtener más información, consulte esta publicación .
fuente
¿Por qué no lo son las macros
const
? Funciones Literales ¿Los nombres de los tipos?const
las cosas son solo un subconjunto de cosas inmutables.Dado que los tipos de referencia son solo eso, tipos, puede haber tenido sentido requerir el
const
calificador-en todos ellos para la simetría con otros tipos (particularmente con tipos de puntero), pero esto se volvería muy tedioso muy rápidamente.Si C ++ tuviera objetos inmutables por defecto, requiriendo la
mutable
palabra clave en cualquier cosa que no quisieraconst
, entonces esto hubiera sido fácil: simplemente no permita que los programadores agreguenmutable
tipos de referencia.Tal como están las cosas, son inmutables sin calificación.
Y, dado que no están
const
calificados, probablemente sería más confuso queis_const
un tipo de referencia arrojara verdadero.Encuentro que esto es un compromiso razonable, especialmente porque la inmutabilidad es impuesta de todos modos por el mero hecho de que no existe sintaxis para mutar una referencia.
fuente
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 utilizan referencias, la semántica de referencia se produce automáticamente y la referencia "se sale del camino"), existen algunas razones defendibles por las que 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. Del lenguaje C, tenemos "clase de almacenamiento" y "vinculación". Se puede introducir un nombre como
extern const int ri
, dondeextern
indica la clase de almacenamiento estático y la presencia de enlace. El tipo es justoconst int
.C ++ obviamente abraza 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 número creciente de atributos no tipográficos que puede exhibir una expresión.
Sin embargo, las referencias son tipos. ¿Por qué?
Se solía explicar en los tutoriales de C ++ que una declaración como
const int &ri
introducidari
tiene tipoconst int
, pero semántica de referencia. Esa semántica de referencia no era un tipo; era simplemente una especie de atributo que indicaba 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 se pueden construir tipos basados en referencias, aunque la sintaxis de construcción de tipos lo permita. Por ejemplo, matrices o punteros a referencias que no son posibles:const int &ari[5]
yconst int &*pri
.Pero, de hecho, las referencias son tipos y, por lo tanto,
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 conremove_reference
.Cuando se utiliza
ri
, la referencia se resuelve de forma transparente, de modo queri
"se ve y se siente comoi
" y se le puede llamar un "alias". En el sistema de tipos, sin embargo,ri
de hecho hay un tipo que es " referencia aconst int
".¿Por qué son 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 cualquier declaración sería adecuada para cualquiera de las definiciones, por
(int)
lo que se debería sospechar que cada función toma una referencia.Del mismo modo, si las referencias no fueran tipos, 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 usara una declaración, y otra unidad de traducción en el mismo programa usara 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: la ABI es la misma. Si dos estructuras o clases tienen el mismo tipo, su diseño es el mismo, así como 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 sencilla incorporarlos al sistema de tipos. (Sin embargo, tenga en cuenta un contraargumento aquí: un miembro de estructura / clase puede ser
static
, lo que también cambia la representación; ¡pero ese no es el tipo!)Por lo tanto, las referencias están en el sistema de tipos como "ciudadanos de segunda clase" (no a diferencia 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 ellas. 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 hay 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 referencias no tienen ningún sentido, porque un puntero levantado de una referencia simplemente va al objeto referenciado. La razón probable por la que no hay matrices de referencias es que la gente de C ++ considera que las matrices son una especie de característica de bajo nivel heredada de C que está rota de muchas formas que son irreparables, y no quieren tocar las matrices como el 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.
const
Tipos no calificables: ¡también se encuentran en ISO C90!Algunas respuestas apuntan al hecho de que las referencias no requieren un
const
calificativo. Eso es más bien una pista falsa, porque la declaraciónconst int &ri = i
ni siquiera intenta hacer unaconst
referencia calificada: es una referencia a un tipo calificado const (que en sí mismo no lo esconst
). Al igual queconst in *ri
declara un puntero a algoconst
, pero ese puntero no lo es en sí mismoconst
.Dicho esto, es cierto que las referencias no pueden llevar el
const
calificador en sí mismas.Sin embargo, esto no es tan extraño. Incluso en el lenguaje ISO C 90, no todos los tipos pueden serlo
const
. Es decir, las matrices no pueden serlo.En primer lugar, la sintaxis no existe para declarar una matriz const:
int a const [42]
es errónea.Sin embargo, lo que la declaración anterior intenta hacer se puede expresar a través de un intermedio
typedef
:typedef int array_t[42]; const array_t a;
Pero esto no hace lo que parece. En esta declaración, no es lo
a
que seconst
califica, ¡sino los elementos! Es decir,a[0]
es unconst int
, peroa
es simplemente "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, de "segunda clase" en el sistema de tipos, como las matrices.
Tenga en cuenta que la analogía es aún más profunda, ya que las matrices también tienen un "comportamiento de conversión invisible", como las referencias. Sin que el programador tenga que utilizar ningún operador explícito, el identificador
a
se convierte automáticamente en unint *
puntero, como si se&a[0]
hubiera utilizado la expresión . Esto es análogo a cómo una referenciari
, cuando la usamos como expresión primaria, denota mágicamente el objetoi
al que está vinculada. Es sólo otro "decaimiento" como el "arreglo al decaimiento del puntero".Y al igual que no debemos confundirnos con la decadencia de "arreglo a puntero" y pensar erróneamente que "los arreglos son solo punteros en C y C ++", tampoco debemos pensar que las referencias son solo alias que no tienen ningún tipo propio.
Cuando
decltype(ri)
suprime la conversión habitual de la referencia a su objeto de referencia, esto no es tan diferente desizeof a
suprimir la conversión de matriz a puntero y operar en el tipo de matriz en sí mismo para calcular su tamaño.fuente
decltype
no realiza esta resolución transparente (no es una función, porri
lo que no se "usa" en el sentido que usted describe). Esto encaja muy bien con todo su enfoque en el sistema de tipos : la conexión clave es quedecltype
es una operación del sistema de tipos .const X & x ”significa que x asigna un alias a un objeto X, pero no puedes cambiar ese objeto X mediante x.
Y vea std :: is_const .
fuente