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
, donde extern
indica la clase de almacenamiento estático y la presencia de enlace. El tipo es justo const 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
introducida ri
tiene tipo const 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]
y const 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 con remove_reference
.
Cuando se utiliza 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". En el sistema de tipos, sin embargo, ri
de hecho hay un tipo que es " referencia a const 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
int x = 0, y = 0;
int &ar[2] = { x, y };
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ón const int &ri = i
ni siquiera intenta hacer una const
referencia calificada: es una referencia a un tipo calificado const (que en sí mismo no lo es const
). Al igual que const in *ri
declara un puntero a algo const
, pero ese puntero no lo es en sí mismo const
.
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 se const
califica, ¡sino los elementos! Es decir, a[0]
es un const int
, pero a
es simplemente "matriz de int". En consecuencia, esto no requiere un diagnóstico:
int *p = a;
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 un int *
puntero, como si se &a[0]
hubiera utilizado la expresión . 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 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 de sizeof a
suprimir la conversión de matriz a puntero y operar en el tipo de matriz en sí mismo para calcular su tamaño.
boolalpha(cout)
es muy inusual. Podría hacerlo en sustd::cout << boolalpha
lugar.