web-dev-qa-db-fra.com

Pourquoi les références ne sont-elles pas "const" en C ++?

Nous savons qu'une "variable const" indique qu'une fois affectée, vous ne pouvez pas changer la variable, comme ceci:

int const i = 1;
i = 2;

Le programme ci-dessus ne parviendra pas à compiler; gcc vous invite avec une erreur:

assignment of read-only variable 'i'

Pas de problème, je peux le comprendre, mais l'exemple suivant est au-delà de 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 changer cette liaison, nous changeons son objet lié. Je suppose donc que le type de ri devrait être le même que i: lorsque i est un int const, pourquoi ri pas const?

85
Troskyvs

Cela peut sembler contre-intuitif, mais je pense que la façon de comprendre cela est de réaliser que, à certains égards, les références sont traitées syntaxiquement comme 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;
}

Sortie:

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 qui est pointé à.

Nous voyons donc correctement la constance du pointeur lui-même renvoyé comme false.

Si nous voulons faire du 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;
}

Sortie:

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 pour n respect crucial, nous ne sommes pas autorisés à relier une référence à un autre objet une fois lié.

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 à le faire 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 un pointeur pourrait (s'il n'est pas déclaré const).

Donc pour répondre à la question:

Q) Pourquoi "référence" n'est pas un "const" en C++?

Dans votre exemple, la syntaxe fait que la chose référencée const est la même que si vous déclariez un pointeur .

A 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

Q) nous savons qu'une fois qu'une référence est liée à un nom/variable, nous ne pouvons pas changer cette liaison, nous changeons son objet lié. Je suppose donc que le type de ri doit être le même que i: lorsque i est un int const, Pourquoi ri n'est pas const?

Pourquoi la decltype() n'est-elle pas transférée à l'objet auquel la référence est liée?

Je suppose que c'est pour l'équivalence sémantique avec pointeurs et peut-être aussi la fonction de decltype() (type déclaré) est de regarder en arrière ce a été déclaré avant la liaison.

52
Galik

pourquoi "ri" n'est-il pas "const"?

std::is_const vérifie si le type est qualifié const ou non.

Si T est un type qualifié const (c'est-à-dire const ou volatile const), la valeur constante de membre est égale à true. Pour tout autre type, la valeur est false.

Mais la référence ne peut pas être qualifiée const. Références [dcl.ref]/1

Les références qualifiées Cv sont mal formées, sauf lorsque les qualificateurs cv sont introduits via l'utilisation d'un nom typedef ([dcl.typedef], [temp.param]) ou d'un spécificateur decltype ([dcl.type.simple]) , auquel cas les qualificatifs cv sont ignorés.

Ainsi, is_const<decltype(ri)>::value renverra false parce que ri (la référence) n'est pas un type qualifié de const. Comme vous l'avez dit, nous ne pouvons pas lier une référence après l'initialisation, ce qui implique que la référence est toujours "const", en revanche, une référence qualifiée const ou une référence non qualifiée const pourrait ne pas avoir de sens en fait.

54
songyuanyao

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, voir cet article .

18
chema989

Pourquoi les macros ne 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 des types, il peut être judicieux d'exiger le qualificatif const sur tous pour la symétrie avec d'autres types (en particulier avec les types pointeurs), mais cela deviendrait très fastidieux très rapidement.

Si C++ avait des objets immuables par défaut, nécessitant le mot clé mutable sur tout ce que vous ne vouliez pas être const , alors cela aurait été facile: ne permettez tout simplement pas aux programmeurs d'ajouter mutable aux types de référence.

En l'état, ils sont immuables sans qualification.

Et, comme ils ne sont pas qualifiés de const-, il serait probablement plus déroutant pour is_const sur un type de référence pour donner la valeur 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.

Il s'agit d'une particularité/fonctionnalité en C++. Bien que nous ne considérions pas les références comme des types, elles se situent 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 du chemin"), il existe des raisons défendables pour lesquelles les références sont modélisées dans le système de type plutôt que comme un attribut distinct en dehors de type.

Tout d'abord, considérons que tous les attributs d'un nom déclaré ne doivent pas être dans le système de types. Du langage C, nous avons "classe de stockage" et "liaison". Un nom peut être introduit sous la forme extern const int ri, Où extern indique une classe de stockage statique et la présence d'une liaison. 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 Introduisait ri comme ayant le type const int, Mais faisait 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 soient pas des types a été utilisé pour justifier 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, les tableaux ou pointeurs vers des références ne sont pas possibles: const int &ari[5] Et const int &*pri.

Mais en fait, les références sont des types et donc decltype(ri) récupère 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 de 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 ressemble à i" et peut être appelé un "alias" pour cela. Dans le système de types, cependant, ri a 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 étaient de type pas, alors ces fonctions seraient 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. S'ils avaient le même type, cela signifie que l'une ou l'autre déclaration conviendrait à l'une ou l'autre définition, et donc chaque fonction (int) Devrait être suspectée de prendre une référence.

De même, si les références n'étaient pas des types, 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 que ne référence implique une différence d'implémentation et il est impossible de le séparer du type, car le type en C++ a à voir avec l'implémentation d'une entité: sa "mise en page" en bits donc pour parler. Si deux fonctions ont le même type, elles peuvent être appelées avec les mêmes conventions d'appel binaires: l'ABI est le 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 modifie également la représentation; mais ce n'est pas du type!)

Ainsi, les références sont dans le système de type en tant que "citoyens de seconde classe" (un peu comme les fonctions et les 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 signifie pas qu'ils ne sont pas des types. Ce ne sont tout simplement pas des types d'une manière sensée.

Toutes ces restrictions de deuxième 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 pas de sens du tout, car un pointeur levé d'une référence va simplement à 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 C++ considèrent les tableaux comme une sorte de fonctionnalité de bas niveau héritée de C qui est rompue de plusieurs façons irréparables, et ils ne veulent pas toucher les tableaux comme base pour quelque chose de nouveau. L'existence de tableaux de références, cependant, fournirait un exemple clair de la façon dont les références doivent être des types.

Types non - const - qualifiables: également trouvés dans ISO C90!

Certaines réponses suggèrent que les références ne prennent pas de qualificatif const. C'est plutôt un hareng rouge, car la déclaration const int &ri = i N'est même pas essayant de faire un const - qualifié référence: 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 qualificatif const elles-mêmes.

Pourtant, ce n'est pas si bizarre. Même dans le langage ISO C 90, tous les types ne peuvent pas ê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é.

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 qui obtient const qualifié, mais les éléments! Autrement dit, a[0] Est un const int, Mais a n'est qu'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 quelque sorte "de seconde classe" dans le système de types, comme les tableaux.

Notez comment l'analogie est encore plus profonde, car 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 pointeur int *, Comme si l'expression &a[0] Avait été utilisée. Ceci 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 une autre "désintégration" comme la "désintégration du tableau vers le pointeur".

Et tout comme nous ne devons pas devenir confus par la décomposition "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 ne sont que 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 Supprimant la conversion de tableau en pointeur et opérant sur type de tableau lui-même pour calculer sa taille.

5
Kaz