Sabemos que uma "variável const" indica que uma vez atribuída, você não pode alterar a variável, assim:
int const i = 1;
i = 2;
O programa acima não será compilado; O gcc apresenta um erro:
assignment of read-only variable 'i'
Sem problemas, posso entender, mas o exemplo a seguir está além da minha compreensão:
#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;
}
Produz
true
false
Esquisito. Sabemos que, uma vez que uma referência é associada a um nome / variável, não podemos alterar essa associação, alteramos seu objeto associado. Portanto, suponho que o tipo de ri
deve ser o mesmo que i
: quando i
é um int const
, por que ri
não é const
?
boolalpha(cout)
é muito incomum. Você poderia fazer emstd::cout << boolalpha
vez disso.ri
ser um "pseudônimo" indistinguível dei
.i
também é uma referência, mas por motivos históricos você não o declara como tal de forma explícita. Assim,i
é uma referência que se refere a um armazenamento eri
é uma referência que se refere ao mesmo armazenamento. Mas não há diferença de natureza entrei
eri
. Como você não pode alterar a vinculação de uma referência, não há necessidade de qualificá-la comoconst
. E deixe-me afirmar que o comentário @Kaz é muito melhor do que a resposta validada (nunca explique as referências usando ponteiros, um ref é um nome, um ptr é uma variável).is_const
retornartrue
neste caso também. Em minha opinião, este é um bom exemplo de por queconst
está fundamentalmente ao contrário; um atributo "mutável" (a la Rustmut
) parece que seria mais consistente.is_const<int const &>::value
falso?" ou similar; Estou lutando para ver qualquer significado para a pergunta além de perguntar sobre o comportamento dos traços de tipoRespostas:
Isso pode parecer contra-intuitivo, mas acho que a maneira de entender isso é perceber que, em certos aspectos, as referências são tratadas sintaticamente como ponteiros .
Isso parece lógico para um indicador :
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; }
Resultado:
true false
Isso é lógico porque sabemos que não é o objeto ponteiro que é const (pode ser feito para apontar para outro lugar), é o objeto que está sendo apontado.
Portanto, vemos corretamente a constância do próprio ponteiro retornado como
false
.Se quisermos fazer o próprio ponteiro
const
, temos que dizer: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; }
Resultado:
true true
E então eu acho que vemos uma analogia sintática com a referência .
No entanto, as referências são semanticamente diferentes dos ponteiros, especialmente em um aspecto crucial, não temos permissão para religar uma referência a outro objeto depois de ligado.
Portanto, embora as referências compartilhem a mesma sintaxe dos ponteiros, as regras são diferentes e a linguagem nos impede de declarar a própria referência
const
desta forma: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; }
Presumo que não tenhamos permissão para fazer isso porque não parece ser necessário quando as regras de linguagem impedem que a referência seja reativada da mesma forma que um ponteiro poderia (se não for declarado
const
).Então, para responder à pergunta:
No seu exemplo, a sintaxe faz com que o objeto seja referido
const
da mesma forma que faria se você estivesse declarando um ponteiro .Certo ou errado, não temos permissão para fazer a referência em si,
const
mas se fôssemos, seria assim:int const& const ri = i; // not allowed
Por que o
decltype()
não é transferido para o objeto ao qual o referece está vinculado?Suponho que isso seja para equivalência semântica com ponteiros e talvez também a função de
decltype()
(tipo declarado) seja olhar para trás, para o que foi declarado antes da ligação ocorrer.fonte
decltype
também e descobriu que não.const
, portanto,std::is_const
deve retornarfalse
. Eles poderiam ter usado, em vez disso, palavras que significam que deve retornartrue
, mas não o fizeram. É isso aí! Todas essas coisas sobre ponteiros, "eu suponho", "eu suponho", etc. não fornecem nenhuma elucidação real.decltype
não é uma operação de tempo de execução, então a ideia "em tempo de execução, as referências se comportam como ponteiros", seja correta ou não, realmente não se aplica.std::is_const
verifica se o tipo é qualificado const ou não.Mas a referência não pode ser qualificada como const. Referências [dcl.ref] / 1
Portanto
is_const<decltype(ri)>::value
, retornaráfalse
porqueri
(a referência) não é um tipo qualificado const. Como você disse, não podemos religar uma referência após a inicialização, o que implica que a referência é sempre "const", por outro lado, referência qualificada const ou referência não qualificada const pode não fazer sentido na verdade.fonte
is_const
retornatrue
? Essa resposta tenta fazer uma analogia de como os ponteiros são opcionalmente recolocáveis, ao passo que as referências não são - e, ao fazer isso, leva à autocontradição pelo mesmo motivo. Não tenho certeza se há uma explicação real de qualquer maneira, além de uma decisão um tanto arbitrária por aqueles que redigem o Padrão, e às vezes é o melhor que podemos esperar. Daí esta resposta.decltype
é uma função e, portanto, funciona diretamente na referência em si, e não no objeto referido. (Isso talvez seja mais relevante para a resposta "referências são basicamente ponteiros", mas ainda acho que é parte do que torna este exemplo confuso e, portanto, vale a pena mencionar aqui.)decltype(name)
age de forma diferente de um generaldecltype(expr)
. Então, por exemplo,decltype(i)
é o tipo declarado dei
que éconst int
, enquantodecltype((i))
seriaint const &
.Você precisa usar
std::remove_reference
para obter o valor que procura.std::cout << std::is_const<std::remove_reference<decltype(ri)>::type>::value << std::endl;
Para mais informações, veja esta postagem .
fonte
Por que as macros não
const
? Funções? Literais? Os nomes dos tipos?const
as coisas são apenas um subconjunto de coisas imutáveis.Visto que os tipos de referência são apenas isso - tipos - pode ter feito algum sentido exigir o
const
-qualificador em todos eles para simetria com outros tipos (particularmente com tipos de ponteiro), mas isso se tornaria muito tedioso muito rapidamente.Se C ++ tivesse objetos imutáveis por padrão, exigindo a
mutable
palavra-chave em qualquer coisa que você não quisesseconst
, então isso teria sido fácil: simplesmente não permita que os programadores adicionemmutable
tipos de referência.Como são, eles são imutáveis sem qualificação.
E, uma vez que eles não são
const
-qualificados, provavelmente seria mais confuso parais_const
um tipo de referência render true.Acho que este é um meio-termo razoável, especialmente porque a imutabilidade é, de qualquer maneira, reforçada pelo mero fato de que não existe sintaxe para alterar uma referência.
fonte
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 queextern
indica a classe de armazenamento estático e a presença de ligação. O tipo é justoconst 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
introduzidari
como tendo tipoconst 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]
econst 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 comremove_reference
.Quando você usa
ri
, a referência é resolvida de forma transparente, para queri
"tenha a aparênciai
" e possa ser chamada de "alias" para ela. No sistema de tipos, entretanto,ri
existe de fato um tipo que é " referência aconst 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
// fantasy syntax int x = 0, y = 0; int &ar[2] = { x, y }; // ar[0] is now an alias for x: could be useful!
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çãoconst int &ri = i
nem mesmo tenta fazer umaconst
referência -qualificada: é uma referência a um tipo const-qualificado (que em si não éconst
). Exatamente comoconst in *ri
declara um ponteiro para algoconst
, 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]
é umconst int
, masa
é apenas "array de int". Consequentemente, isso não requer um diagnóstico:int *p = a; /* surprise! */
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 umint *
ponteiro, como se a expressão&a[0]
tivesse sido usada. Isso é análogo a como uma referênciari
, quando a usamos como uma expressão primária, denota magicamente o objetoi
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 desizeof a
suprimir a conversão de array em ponteiro e operar no próprio tipo de array para calcular seu tamanho.fonte
decltype
não executa essa resolução transparente (não é uma função, portanto,ri
não é "usada" no sentido que você descreve). Isso se encaixa muito bem com todo o seu foco no sistema de tipos - a conexão principal é quedecltype
é uma operação do sistema de tipos .const X & x ”significa x aliases a um objeto X, mas você não pode mudar esse objeto X via x.
E veja std :: is_const .
fonte