C'est une particularité / fonctionnalité en C ++. Bien que nous ne considérions pas les références comme des types, elles «se trouvent» en fait dans le système de types. Bien que cela semble gênant (étant donné que lorsque des références sont utilisées, la sémantique de référence se produit automatiquement et la référence «s'écarte»), il y a des raisons défendables pour lesquelles les références sont modélisées dans le système de types plutôt que comme un attribut séparé en dehors de type.
Tout d'abord, considérons que tous les attributs d'un nom déclaré ne doivent pas nécessairement être dans le système de types. Du langage C, nous avons "classe de stockage" et "liaison". Un nom peut être introduit comme extern const int ri
, où le extern
indique la classe de stockage statique et la présence d'un lien. Le type est juste const int
.
C ++ embrasse évidemment la notion que les expressions ont des attributs qui sont en dehors du système de types. Le langage a maintenant un concept de «classe de valeur» qui est une tentative d'organiser le nombre croissant d'attributs non-type qu'une expression peut présenter.
Pourtant, les références sont des types. Pourquoi?
Il était expliqué dans les didacticiels C ++ qu'une déclaration comme const int &ri
introduite ri
comme ayant un type const int
, mais faisant référence à la sémantique. Cette sémantique de référence n'était pas un type; c'était simplement une sorte d'attribut indiquant une relation inhabituelle entre le nom et l'emplacement de stockage. De plus, le fait que les références ne sont pas des types a été utilisé pour expliquer pourquoi vous ne pouvez pas construire de types basés sur des références, même si la syntaxe de construction de type le permet. Par exemple, des tableaux ou des pointeurs vers des références n'étant pas possibles: const int &ari[5]
et const int &*pri
.
Mais en fait, les références sont des types et decltype(ri)
récupèrent ainsi un nœud de type référence qui n'est pas qualifié. Vous devez descendre au-delà de ce nœud dans l'arborescence des types pour accéder au type sous-jacent avec remove_reference
.
Lorsque vous utilisez ri
, la référence est résolue de manière transparente, de sorte que ri
"ressemble et se sent comme i
" et peut être appelée un "alias" pour elle. Dans le système de types, cependant, a ri
en fait un type qui est " référence à const int
".
Pourquoi les types de références?
Considérez que si les références n'étaient pas des types, ces fonctions seraient alors considérées comme ayant le même type:
void foo(int);
void foo(int &);
Cela ne peut tout simplement pas être pour des raisons qui vont de soi. Si elles avaient le même type, cela signifie que l'une ou l'autre des déclarations conviendrait à l'une ou l'autre définition, et donc chaque (int)
fonction devrait être soupçonnée de prendre une référence.
De même, si les références n'étaient pas des types, alors ces deux déclarations de classe seraient équivalentes:
class foo {
int m;
};
class foo {
int &m;
};
Il serait correct qu'une unité de traduction utilise une déclaration et qu'une autre unité de traduction du même programme utilise l'autre déclaration.
Le fait est qu'une référence implique une différence d'implémentation et il est impossible de séparer cela du type, car le type en C ++ a à voir avec l'implémentation d'une entité: sa «disposition» en bits pour ainsi dire. Si deux fonctions ont le même type, elles peuvent être appelées avec les mêmes conventions d'appel binaire: l'ABI est la même. Si deux structures ou classes ont le même type, leur disposition est la même ainsi que la sémantique d'accès à tous les membres. La présence de références modifie ces aspects des types, et c'est donc une décision de conception simple de les incorporer dans le système de types. (Cependant, notez un contre-argument ici: un membre struct / class peut être static
, ce qui change également la représentation; mais ce n'est pas du type!)
Ainsi, les références sont dans le système de types en tant que «citoyens de seconde classe» (pas contrairement aux fonctions et tableaux dans ISO C). Il y a certaines choses que nous ne pouvons pas «faire» avec des références, comme déclarer des pointeurs vers des références ou des tableaux de celles-ci. Mais cela ne veut pas dire qu'ils ne sont pas des types. Ils ne sont tout simplement pas des types d'une manière logique.
Toutes ces restrictions de seconde classe ne sont pas essentielles. Étant donné qu'il existe des structures de références, il pourrait y avoir des tableaux de références! Par exemple
int x = 0, y = 0;
int &ar[2] = { x, y };
Ce n'est tout simplement pas implémenté en C ++, c'est tout. Les pointeurs vers des références n'ont cependant aucun sens, car un pointeur soulevé d'une référence va simplement vers l'objet référencé. La raison probable pour laquelle il n'y a pas de tableaux de références est que les gens de C ++ considèrent les tableaux comme une sorte de fonctionnalité de bas niveau héritée de C qui est cassée de nombreuses manières qui sont irréparables, et ils ne veulent pas toucher les tableaux comme le base pour quelque chose de nouveau. L'existence de tableaux de références, cependant, serait un exemple clair de la façon dont les références doivent être des types.
const
Types non qualifiables: également présents dans l'ISO C90!
Certaines réponses suggèrent que les références ne prennent pas de const
qualificatif. C'est plutôt un hareng rouge, car la déclaration const int &ri = i
ne tente même pas de faire une const
référence qualifiée: c'est une référence à un type qualifié const (qui n'est pas lui-même const
). Tout comme const in *ri
déclare un pointeur vers quelque chose const
, mais ce pointeur n'est pas lui-même const
.
Cela dit, il est vrai que les références ne peuvent pas porter le const
qualificatif elles-mêmes.
Pourtant, ce n'est pas si bizarre. Même dans le langage ISO C 90, tous les types ne peuvent pas l'être const
. À savoir, les tableaux ne peuvent pas l'être.
Premièrement, la syntaxe n'existe pas pour déclarer un tableau const: int a const [42]
est erronée.
Cependant, ce que la déclaration ci-dessus essaie de faire peut être exprimé via un intermédiaire typedef
:
typedef int array_t[42];
const array_t a;
Mais cela ne fait pas ce à quoi il ressemble. Dans cette déclaration, ce n'est pas a
ce qui est const
qualifié, mais les éléments! C'est-à-dire que a[0]
c'est un const int
, mais a
c'est juste un "tableau d'int". Par conséquent, cela ne nécessite pas de diagnostic:
int *p = a;
Cela fait:
a[0] = 1;
Encore une fois, cela souligne l'idée que les références sont en un certain sens «de seconde classe» dans le système de types, comme les tableaux.
Notez comment l'analogie est encore plus profonde, puisque les tableaux ont également un "comportement de conversion invisible", comme les références. Sans que le programmeur n'ait à utiliser d'opérateur explicite, l'identifiant a
se transforme automatiquement en int *
pointeur, comme si l'expression &a[0]
avait été utilisée. C'est analogue à la façon dont une référence ri
, lorsque nous l'utilisons comme expression principale, désigne comme par magie l'objet i
auquel elle est liée. C'est juste un autre "decay" comme le "array to pointer decay".
Et tout comme nous ne devons pas nous confondre avec la désintégration «tableau vers pointeur» en pensant à tort que «les tableaux ne sont que des pointeurs en C et C ++», nous ne devons pas non plus penser que les références sont juste des alias qui n'ont pas de type propre.
Lorsque decltype(ri)
supprime la conversion habituelle de la référence en son objet référent, ce n'est pas si différent de sizeof a
supprimer la conversion tableau en pointeur et d'opérer sur le type de tableau lui-même pour calculer sa taille.
boolalpha(cout)
c'est très inhabituel. Vous pourriez faire à lastd::cout << boolalpha
place.