Esta é uma peculiaridade / característica do C ++. Embora não pensemos nas referências como tipos, elas na verdade "assentam" no sistema de tipos. Embora isso pareça estranho (dado que quando referências são usadas, a semântica de referência ocorre automaticamente e a referência "sai do caminho"), existem algumas razões defensáveis pelas quais as referências são modeladas no sistema de tipos em vez de como um atributo separado fora de tipo.
Em primeiro lugar, vamos considerar que nem todos os atributos de um nome declarado devem estar no sistema de tipos. Da linguagem C, temos "classe de armazenamento" e "ligação". Um nome pode ser introduzido como extern const int ri
, em que extern
indica a classe de armazenamento estático e a presença de ligação. O tipo é justo const int
.
C ++ obviamente adota a noção de que as expressões têm atributos que estão fora do sistema de tipos. A linguagem agora tem um conceito de "classe de valor", que é uma tentativa de organizar o número crescente de atributos não-tipo que uma expressão pode exibir.
No entanto, as referências são tipos. Por quê?
Costumava ser explicado em tutoriais C ++ que uma declaração como const int &ri
introduzida ri
como tendo tipo const int
, mas semântica de referência. Essa semântica de referência não era um tipo; era simplesmente uma espécie de atributo que indica uma relação incomum entre o nome e o local de armazenamento. Além disso, o fato de que as referências não são tipos foi usado para racionalizar por que você não pode construir tipos com base em referências, embora a sintaxe de construção de tipo permita. Por exemplo, arrays ou ponteiros para referências não são possíveis: const int &ari[5]
e const int &*pri
.
Mas, na verdade, as referências são tipos e, portanto, decltype(ri)
recupera algum nó de tipo de referência que não está qualificado. Você deve descer além deste nó na árvore de tipos para chegar ao tipo subjacente com remove_reference
.
Quando você usa ri
, a referência é resolvida de forma transparente, para que ri
"tenha a aparência i
" e possa ser chamada de "alias" para ela. No sistema de tipos, entretanto, ri
existe de fato um tipo que é " referência a const int
".
Por que são tipos de referências?
Considere que se as referências não fossem tipos, essas funções seriam consideradas como tendo o mesmo tipo:
void foo(int);
void foo(int &);
Isso simplesmente não pode ser por razões que são bastante evidentes. Se eles tivessem o mesmo tipo, isso significa que qualquer uma das declarações seria adequada para qualquer uma das definições e, portanto, cada (int)
função teria que ser suspeita de receber uma referência.
Da mesma forma, se as referências não fossem tipos, essas duas declarações de classe seriam equivalentes:
class foo {
int m;
};
class foo {
int &m;
};
Seria correto que uma unidade de tradução usasse uma declaração e outra unidade de tradução no mesmo programa usasse a outra declaração.
O fato é que uma referência implica uma diferença na implementação e é impossível separar isso do tipo, porque o tipo em C ++ tem a ver com a implementação de uma entidade: seu "layout" em bits, por assim dizer. Se duas funções tiverem o mesmo tipo, elas podem ser chamadas com as mesmas convenções de chamada binária: a ABI é a mesma. Se duas estruturas ou classes têm o mesmo tipo, seu layout é o mesmo, assim como a semântica de acesso a todos os membros. A presença de referências altera esses aspectos dos tipos e, portanto, é uma decisão de design simples incorporá-los ao sistema de tipos. (No entanto, observe um contra-argumento aqui: um membro de estrutura / classe pode ser static
, o que também altera a representação; ainda que não seja o tipo!)
Assim, as referências estão no sistema de tipos como "cidadãos de segunda classe" (não muito diferentes de funções e matrizes em ISO C). Existem certas coisas que não podemos "fazer" com referências, como declarar ponteiros para referências ou matrizes deles. Mas isso não significa que não sejam tipos. Eles simplesmente não são tipos de uma forma que faça sentido.
Nem todas essas restrições de segunda classe são essenciais. Dado que existem estruturas de referências, pode haver matrizes de referências! Por exemplo
int x = 0, y = 0;
int &ar[2] = { x, y };
Isso simplesmente não é implementado em C ++, só isso. Porém, ponteiros para referências não fazem sentido, porque um ponteiro levantado de uma referência apenas vai para o objeto referenciado. A provável razão pela qual não há matrizes de referências é que as pessoas de C ++ consideram matrizes como um tipo de recurso de baixo nível herdado de C que está quebrado de muitas maneiras que são irreparáveis, e eles não querem mexer em matrizes como o base para qualquer coisa nova. A existência de matrizes de referências, entretanto, seria um exemplo claro de como as referências devem ser tipos.
const
Tipos não qualificáveis: também encontrados na ISO C90!
Algumas respostas estão sugerindo o fato de que as referências não precisam de um const
qualificador. Isso é uma pista falsa, porque a declaração const int &ri = i
nem mesmo tenta fazer uma const
referência -qualificada: é uma referência a um tipo const-qualificado (que em si não é const
). Exatamente como const in *ri
declara um ponteiro para algo const
, mas esse ponteiro não é const
.
Dito isso, é verdade que as const
próprias referências não podem conter o qualificador.
No entanto, isso não é tão bizarro. Mesmo na linguagem ISO C 90, nem todos os tipos podem ser const
. Ou seja, as matrizes não podem ser.
Em primeiro lugar, a sintaxe não existe para declarar uma matriz const: int a const [42]
está errada.
No entanto, o que a declaração acima está tentando fazer pode ser expresso por meio de um intermediário typedef
:
typedef int array_t[42];
const array_t a;
Mas isso não faz o que parece. Nesta declaração, não é o a
que é const
qualificado, mas os elementos! Ou seja, a[0]
é um const int
, mas a
é apenas "array de int". Consequentemente, isso não requer um diagnóstico:
int *p = a;
Isso faz:
a[0] = 1;
Novamente, isso reforça a ideia de que as referências são, em certo sentido, "segunda classe" no sistema de tipos, como os arrays.
Observe como a analogia é ainda mais profunda, já que os arrays também têm um "comportamento de conversão invisível", como as referências. Sem que o programador precise usar nenhum operador explícito, o identificador se a
transforma automaticamente em um int *
ponteiro, como se a expressão &a[0]
tivesse sido usada. Isso é análogo a como uma referência ri
, quando a usamos como uma expressão primária, denota magicamente o objeto i
ao qual está ligada. É apenas outro "decaimento" como o "decaimento de array para ponteiro".
E, assim como não devemos ficar confusos com o declínio de "array para ponteiro" em pensar erroneamente que "arrays são apenas ponteiros em C e C ++", também não devemos pensar que as referências são apenas apelidos que não têm nenhum tipo próprio.
Quando decltype(ri)
suprime a conversão usual da referência ao seu objeto de referência, isso não é tão diferente de sizeof a
suprimir a conversão de array em ponteiro e operar no próprio tipo de array para calcular seu tamanho.
boolalpha(cout)
é muito incomum. Você poderia fazer emstd::cout << boolalpha
vez disso.