Questa è una stranezza / funzionalità in C ++. Sebbene non pensiamo ai riferimenti come tipi, in realtà "siedono" nel sistema dei tipi. Sebbene questo sembri imbarazzante (dato che quando vengono utilizzati i riferimenti, la semantica di riferimento si verifica automaticamente e il riferimento "si allontana"), ci sono alcune ragioni difendibili per cui i riferimenti sono modellati nel sistema dei tipi invece che come un attributo separato al di fuori di genere.
In primo luogo, consideriamo che non tutti gli attributi di un nome dichiarato devono essere nel sistema dei tipi. Dal linguaggio C, abbiamo "classe di archiviazione" e "collegamento". Un nome può essere introdotto come extern const int ri
, dove extern
indica la classe di memoria statica e la presenza di collegamento. Il tipo è giusto const int
.
C ++ ovviamente abbraccia l'idea che le espressioni abbiano attributi che sono al di fuori del sistema di tipi. Il linguaggio ora ha un concetto di "classe di valore" che è un tentativo di organizzare il numero crescente di attributi non di tipo che un'espressione può esibire.
Eppure i riferimenti sono tipi. Perché?
Nelle esercitazioni C ++ veniva spiegato che una dichiarazione come const int &ri
introduceva ri
come tipo const int
, ma faceva riferimento alla semantica. Quella semantica di riferimento non era un tipo; era semplicemente una specie di attributo che indicava una relazione insolita tra il nome e la posizione di archiviazione. Inoltre, il fatto che i riferimenti non siano tipi è stato utilizzato per razionalizzare il motivo per cui non è possibile costruire tipi basati sui riferimenti, anche se la sintassi di costruzione del tipo lo consente. Ad esempio, array o puntatori a riferimenti non sono possibili: const int &ari[5]
e const int &*pri
.
Ma in realtà i riferimenti sono tipi e quindi decltype(ri)
recupera alcuni nodi del tipo di riferimento che non sono qualificati. È necessario scendere oltre questo nodo nell'albero dei tipi per raggiungere il tipo sottostante con remove_reference
.
Quando si utilizza ri
, il riferimento viene risolto in modo trasparente, in modo che ri
"sembra e si sente come i
" e può essere chiamato un "alias" per esso. Nel sistema dei tipi, però, ri
ha in effetti un tipo che è " riferimento a const int
".
Perché sono tipi di referenze?
Considera che se i riferimenti non fossero tipi, allora queste funzioni sarebbero considerate avere lo stesso tipo:
void foo(int);
void foo(int &);
Questo semplicemente non può essere per ragioni che sono abbastanza evidenti. Se avessero lo stesso tipo, ciò significa che entrambe le dichiarazioni sarebbero adatte per entrambe le definizioni, quindi si (int)
dovrebbe sospettare che ogni funzione prenda un riferimento.
Allo stesso modo, se i riferimenti non fossero tipi, queste due dichiarazioni di classe sarebbero equivalenti:
class foo {
int m;
};
class foo {
int &m;
};
Sarebbe corretto che un'unità di traduzione utilizzi una dichiarazione e un'altra unità di traduzione nello stesso programma utilizzi l'altra dichiarazione.
Il fatto è che un riferimento implica una differenza nell'implementazione ed è impossibile separarlo dal tipo, perché il tipo in C ++ ha a che fare con l'implementazione di un'entità: il suo "layout" in bit per così dire. Se due funzioni hanno lo stesso tipo, possono essere invocate con le stesse convenzioni di chiamata binaria: l'ABI è lo stesso. Se due strutture o classi hanno lo stesso tipo, il loro layout è lo stesso così come la semantica di accesso a tutti i membri. La presenza di riferimenti modifica questi aspetti dei tipi, quindi è una decisione progettuale semplice incorporarli nel sistema di tipi. (Tuttavia, nota un controargomentazione qui: può essere un membro della struttura / classe static
, che cambia anche la rappresentazione; ma non è tipo!)
Pertanto, i riferimenti sono nel sistema dei tipi come "cittadini di seconda classe" (non diversamente dalle funzioni e dagli array in ISO C). Ci sono alcune cose che non possiamo "fare" con i riferimenti, come dichiarare puntatori a riferimenti o array di essi. Ma questo non significa che non siano tipi. Semplicemente non sono tipi in un modo che abbia senso.
Non tutte queste restrizioni di seconda classe sono essenziali. Dato che ci sono strutture di riferimenti, potrebbero esserci matrici di riferimenti! Per esempio
int x = 0, y = 0;
int &ar[2] = { x, y };
Questo non è implementato in C ++, tutto qui. I puntatori ai riferimenti non hanno alcun senso, tuttavia, perché un puntatore sollevato da un riferimento va semplicemente all'oggetto referenziato. La probabile ragione per cui non ci sono array di riferimenti è che le persone C ++ considerano gli array come una sorta di funzionalità di basso livello ereditata da C che è rotta in molti modi che sono irreparabili e non vogliono toccare gli array come il base per qualcosa di nuovo. L'esistenza di matrici di riferimenti, tuttavia, sarebbe un chiaro esempio di come i riferimenti devono essere tipi.
const
Tipi non qualificabili: presenti anche in ISO C90!
Alcune risposte suggeriscono che i riferimenti non richiedono un const
qualificatore. Questa è piuttosto una falsa pista , perché la dichiarazione const int &ri = i
non sta nemmeno tentando di fare un const
riferimento -qualified: è un riferimento a un tipo qualificato const (che di per sé non lo è const
). Proprio come const in *ri
dichiara un puntatore a qualcosa const
, ma quel puntatore di per sé non lo è const
.
Detto questo, è vero che i riferimenti non possono portare il const
qualificatore da soli.
Tuttavia, questo non è così bizzarro. Anche nel linguaggio ISO C 90, non tutti i tipi possono essere const
. Vale a dire, gli array non possono essere.
In primo luogo, la sintassi non esiste per dichiarare un array const: int a const [42]
è errata.
Tuttavia, ciò che la dichiarazione di cui sopra sta cercando di fare può essere espresso tramite un intermedio typedef
:
typedef int array_t[42];
const array_t a;
Ma questo non fa quello che sembra. In questa dichiarazione, non è ciò a
che viene const
qualificato, ma gli elementi! Vale a dire, a[0]
è un const int
, ma a
è solo un "array di int". Di conseguenza, questo non richiede una diagnosi:
int *p = a;
Questo fa:
a[0] = 1;
Ancora una volta, questo sottolinea l'idea che i riferimenti sono in un certo senso "seconda classe" nel sistema di tipi, come gli array.
Si noti come l'analogia sia ancora più profonda, poiché gli array hanno anche un "comportamento di conversione invisibile", come i riferimenti. Senza che il programmatore debba utilizzare alcun operatore esplicito, l'identificatore a
si trasforma automaticamente in un int *
puntatore, come se l'espressione &a[0]
fosse stata utilizzata. Questo è analogo a come un riferimento ri
, quando lo usiamo come espressione primaria, denota magicamente l'oggetto i
a cui è legato. È solo un altro "decadimento" come il "decadimento da matrice a puntatore".
E proprio come non dobbiamo farci confondere dal decadimento "da array a puntatore" facendoci pensare erroneamente che "gli array sono solo puntatori in C e C ++", allo stesso modo non dobbiamo pensare che i riferimenti siano solo alias che non hanno un tipo proprio.
Quando decltype(ri)
sopprime la normale conversione del riferimento al suo oggetto referente, non è così diverso dal sizeof a
sopprimere la conversione da matrice a puntatore e dall'operare sul tipo di matrice stesso per calcolarne le dimensioni.
boolalpha(cout)
è molto insolito. Potresti farestd::cout << boolalpha
invece.