Nous savons qu'une "variable const" indique qu'une fois assignée, vous ne pouvez pas changer la variable, comme ceci:
int const i = 1;
i = 2;
Le programme ci-dessus échouera à se compiler; gcc invite avec une erreur:
assignment of read-only variable 'i'
Pas de problème, je peux le comprendre, mais l'exemple suivant dépasse ma compréhension:
#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;
}
Il sort
true
false
Bizarre. Nous savons qu'une fois qu'une référence est liée à un nom / variable, nous ne pouvons pas modifier cette liaison, nous changeons son objet lié. Donc, je suppose que le type de ri
devrait être le même que i
: quand i
est un int const
, pourquoi ri
pas const
?
boolalpha(cout)
c'est très inhabituel. Vous pourriez faire à lastd::cout << boolalpha
place.ri
être un "alias" indiscernablei
.i
est également une référence mais pour des raisons historiques, vous ne le déclarez pas comme tel de manière explicite. Ainsii
est une référence qui fait référence à un stockage etri
est une référence qui fait référence au même stockage. Mais il n'y a pas de différence de nature entrei
etri
. Comme vous ne pouvez pas modifier la liaison d'une référence, il n'est pas nécessaire de la qualifier commeconst
. Et laissez-moi affirmer que le commentaire @Kaz est bien meilleur que la réponse validée (n'expliquez jamais les références à l'aide de pointeurs, un ref est un nom, un ptr est une variable).is_const
à revenirtrue
dans ce cas également. À mon avis, c'est un bon exemple de pourquoiconst
est fondamentalement en arrière; un attribut «mutable» (à la Rustmut
) semble qu'il serait plus cohérent.is_const<int const &>::value
faux?" ou similaire; J'ai du mal à voir un sens à la question autre que de poser des questions sur le comportement des traits de typeRéponses:
Cela peut sembler contre-intuitif, mais je pense que la manière de comprendre cela est de se rendre compte que, à certains égards, les références sont traitées syntaxiquement comme des pointeurs .
Cela semble logique pour un pointeur :
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; }
Production:
true false
C'est logique car nous savons que ce n'est pas l' objet pointeur qui est const (on peut le faire pointer ailleurs), c'est l'objet vers lequel pointe.
Nous voyons donc correctement la constness du pointeur lui-même renvoyé comme
false
.Si nous voulons créer le pointeur lui-même,
const
nous devons dire: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; }
Production:
true true
Et donc je pense que nous voyons une analogie syntaxique avec la référence .
Cependant, les références sont sémantiquement différentes des pointeurs, en particulier sur un point crucial, nous ne sommes pas autorisés à relier une référence à un autre objet une fois liée.
Ainsi, même si les références partagent la même syntaxe que les pointeurs, les règles sont différentes et donc le langage nous empêche de déclarer la référence elle-même
const
comme ceci: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; }
Je suppose que nous ne sommes pas autorisés à faire cela car cela ne semble pas être nécessaire lorsque les règles de langage empêchent la référence d'être rebondie de la même manière qu'un pointeur le pourrait (s'il n'est pas déclaré
const
).Donc pour répondre à la question:
Dans votre exemple, la syntaxe fait référence à
const
l' objet de la même manière que si vous déclariez un pointeur .À tort ou à raison, nous ne sommes pas autorisés à faire la référence elle-même,
const
mais si nous l'étions, cela ressemblerait à ceci:int const& const ri = i; // not allowed
Pourquoi le n'est-il
decltype()
pas transféré à l'objet auquel la référence est liée?Je suppose que c'est pour l'équivalence sémantique avec des pointeurs et peut-être aussi que la fonction de
decltype()
(type déclaré) est de revenir sur ce qui a été déclaré avant que la liaison n'ait lieu.la source
decltype
également et a constaté que non.const
, doncstd::is_const
doit revenirfalse
. Ils auraient pu à la place utiliser un libellé qui signifiait qu'il devait revenirtrue
, mais ils ne l'ont pas fait. C'est ça! Tout ce truc sur les pointeurs, "je suppose", "je suppose", etc. ne fournit aucune explication réelle.decltype
n'est pas une opération d'exécution, donc l'idée "à l'exécution, les références se comportent comme des pointeurs", qu'elle soit correcte ou non, ne s'applique pas vraiment.std::is_const
vérifie si le type est qualifié const ou non.Mais la référence ne peut pas être qualifiée const. Références [dcl.ref] / 1
Donc
is_const<decltype(ri)>::value
retournerafalse
parce queri
(la référence) n'est pas un type qualifié const. Comme vous l'avez dit, nous ne pouvons pas relier une référence après l'initialisation, ce qui implique que la référence est toujours "const", d'un autre côté, une référence qualifiée par const ou une référence non qualifiée par const peut ne pas avoir de sens en fait.la source
is_const
revient pastrue
? Cette réponse tente de faire une analogie avec la façon dont les pointeurs peuvent éventuellement être réinstallés, alors que les références ne le sont pas - et, ce faisant, conduit à une auto-contradiction pour la même raison. Je ne suis pas sûr qu'il y ait une véritable explication de toute façon, autre qu'une décision quelque peu arbitraire de ceux qui rédigent le Standard, et parfois c'est la meilleure que nous puissions espérer. D'où cette réponse.decltype
n'est pas une fonction et donc travaille directement sur la référence elle-même plutôt que sur l'objet référencé. (Ceci est peut-être plus pertinent pour la réponse "les références sont essentiellement des pointeurs", mais je pense toujours que cela fait partie de ce qui rend cet exemple déroutant et mérite donc d'être mentionné ici.)decltype(name)
agit différemment d'un généraldecltype(expr)
. Ainsi, par exemple,decltype(i)
le type déclaréi
est-ilconst int
, alors que ledecltype((i))
seraitint const &
.Vous devez utiliser
std::remove_reference
pour obtenir la valeur que vous recherchez.std::cout << std::is_const<std::remove_reference<decltype(ri)>::type>::value << std::endl;
Pour plus d'informations, consultez cet article .
la source
Pourquoi les macros ne le sont-elles pas
const
? Les fonctions? Littéraux? Les noms des types?const
les choses ne sont qu'un sous-ensemble de choses immuables.Étant donné que les types de référence ne sont que cela - des types - il aurait peut-être été judicieux d'exiger le
const
qualificatif-sur tous pour la symétrie avec d'autres types (en particulier avec les types de pointeur), mais cela deviendrait très fastidieux très rapidement.Si C ++ avait des objets immuables par défaut, nécessitant le
mutable
mot clé sur tout ce que vous ne vouliez pas êtreconst
, alors cela aurait été facile: ne permettez simplement pas aux programmeurs d'ajoutermutable
des types de référence.En l'état, ils sont immuables sans réserve.
Et, comme ils ne sont pas
const
qualifiés, il serait probablement plus déroutant pouris_const
un type de référence de donner true.Je trouve que c'est un compromis raisonnable, d'autant plus que l'immuabilité est de toute façon imposée par le simple fait qu'aucune syntaxe n'existe pour muter une référence.
la source
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ù leextern
indique la classe de stockage statique et la présence d'un lien. Le type est justeconst 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
introduiteri
comme ayant un typeconst 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]
etconst 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 avecremove_reference
.Lorsque vous utilisez
ri
, la référence est résolue de manière transparente, de sorte queri
"ressemble et se sent commei
" et peut être appelée un "alias" pour elle. Dans le système de types, cependant, ari
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
// fantasy syntax int x = 0, y = 0; int &ar[2] = { x, y }; // ar[0] is now an alias for x: could be useful!
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éclarationconst int &ri = i
ne tente même pas de faire uneconst
référence qualifiée: c'est une référence à un type qualifié const (qui n'est pas lui-mêmeconst
). Tout commeconst in *ri
déclare un pointeur vers quelque choseconst
, mais ce pointeur n'est pas lui-mêmeconst
.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 estconst
qualifié, mais les éléments! C'est-à-dire quea[0]
c'est unconst int
, maisa
c'est juste un "tableau d'int". Par conséquent, cela ne nécessite pas de diagnostic:int *p = a; /* surprise! */
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 enint *
pointeur, comme si l'expression&a[0]
avait été utilisée. C'est analogue à la façon dont une référenceri
, lorsque nous l'utilisons comme expression principale, désigne comme par magie l'objeti
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 desizeof a
supprimer la conversion tableau en pointeur et d'opérer sur le type de tableau lui-même pour calculer sa taille.la source
decltype
n'effectue pas cette résolution transparente (ce n'est pas une fonction, doncri
n'est pas «utilisé» dans le sens que vous décrivez). Cela correspond très bien à votre concentration sur le système de type - leur connexion clé est qu'ildecltype
s'agit d' une opération de type-système .const X & x »signifie que x alias un objet X, mais vous ne pouvez pas changer cet objet X via x.
Et voir std :: is_const .
la source