Warum sind Referenzen in C ++ nicht "const"?

86

Wir wissen, dass eine "const-Variable" angibt, dass Sie die Variable nach ihrer Zuweisung nicht wie folgt ändern können:

int const i = 1;
i = 2;

Das obige Programm kann nicht kompiliert werden. gcc fordert mit einem Fehler auf:

assignment of read-only variable 'i'

Kein Problem, ich kann es verstehen, aber das folgende Beispiel ist für mich unverständlich:

#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;
}

Es gibt aus

true
false

Seltsam. Wir wissen, dass wir, sobald eine Referenz an einen Namen / eine Variable gebunden ist, diese Bindung nicht mehr ändern können, sondern ihr gebundenes Objekt. Ich nehme an, der Typ risollte der gleiche sein wie i: Wann iist ein int const, warum rinicht const?

Troskyvs
quelle
3
Auch boolalpha(cout)ist sehr ungewöhnlich. Sie könnten std::cout << boolalphastattdessen tun .
Isanae
6
Soviel dazu ri, ein "Alias" zu sein, von dem man nicht unterscheiden kann i.
Kaz
1
Dies liegt daran, dass eine Referenz immer an dasselbe Objekt gebunden ist. iist auch eine Referenz, aber aus historischen Gründen deklarieren Sie sie nicht explizit als solche. Dies iist also eine Referenz, die sich auf einen Speicher bezieht, und rieine Referenz, die sich auf denselben Speicher bezieht. Aber es gibt keinen Unterschied in der Natur zwischen i und ri. Da Sie die Bindung einer Referenz nicht ändern können, müssen Sie sie nicht als qualifizieren const. Und lassen Sie mich behaupten, dass der @ Kaz-Kommentar viel besser ist als die validierte Antwort (erklären Sie Referenzen niemals mit Zeigern, ein ref ist ein Name, ein ptr ist eine Variable).
Jean-Baptiste Yunès
1
Fantastische Frage. Bevor ich dieses Beispiel gesehen habe, hätte ich erwartet is_const, auch truein diesem Fall zurückzukehren. Meiner Meinung nach ist dies ein gutes Beispiel dafür, warum constes grundsätzlich rückwärts geht. Ein "veränderliches" Attribut (a la Rust mut) scheint konsistenter zu sein.
Kyle Strand
1
Vielleicht sollte der Titel in "Warum ist is_const<int const &>::valuefalsch?" Geändert werden. o.ä; Ich kämpfe darum, eine andere Bedeutung für die Frage zu erkennen, als nach dem Verhalten von Typmerkmalen zu fragen
MM

Antworten:

52

Dies mag kontraintuitiv erscheinen, aber ich denke, der Weg, dies zu verstehen, besteht darin, zu erkennen, dass Referenzen in gewisser Hinsicht syntaktisch wie Zeiger behandelt werden .

Dies scheint für einen Zeiger logisch :

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;
}

Ausgabe:

true
false

Dies ist logisch, da wir wissen, dass es nicht das Zeigerobjekt ist, das const ist (es kann dazu gebracht werden, auf eine andere Stelle zu zeigen), sondern das Objekt, auf das gezeigt wird.

Wir sehen also die Konstanz des Zeigers selbst korrekt als zurückgegeben false.

Wenn wir den Zeiger selbst machen wollen, müssen constwir sagen:

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;
}

Ausgabe:

true
true

Ich denke, wir sehen eine syntaktische Analogie mit der Referenz .

Jedoch Referenzen semantisch anders Zeiger besonders in sind ein entscheidender Hinsicht sind wir nicht erlaubt zu binden einen Verweis auf ein anderes Objekt einmal gebunden.

Obwohl Referenzen dieselbe Syntax wie Zeiger haben, sind die Regeln unterschiedlich und die Sprache hindert uns daran, die Referenz selbst constwie folgt zu deklarieren :

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;
}

Ich gehe davon aus, dass wir dies nicht dürfen, da dies nicht erforderlich zu sein scheint, wenn die Sprachregeln verhindern, dass die Referenz auf die gleiche Weise wie ein Zeiger zurückgebunden wird (wenn sie nicht deklariert ist const).

Um die Frage zu beantworten:

F) Warum ist "Referenz" in C ++ keine "Konstante"?

In Ihrem Beispiel bewirkt die Syntax, dass auf das Objekt constgenauso verwiesen wird, wie wenn Sie einen Zeiger deklarieren würden .

Zu Recht oder zu Unrecht dürfen wir die Referenz nicht selbst machen, constaber wenn wir es wären, würde es so aussehen:

int const& const ri = i; // not allowed

F) Sobald eine Referenz an einen Namen / eine Variable gebunden ist, können wir diese Bindung nicht mehr ändern, sondern ihr gebundenes Objekt. Ich nehme an, die Art von risollte dieselbe sein wie i: Wann iist a int const, warum rinicht const?

Warum wird das decltype()nicht auf das Objekt übertragen, an das der Schiedsrichter gebunden ist?

Ich nehme an, dies dient der semantischen Äquivalenz mit Zeigern, und möglicherweise besteht die Funktion von decltype()(deklarierter Typ) darin, auf das zurückzublicken, was vor der Bindung deklariert wurde .

Galik
quelle
13
Es hört sich so an, als würden Sie sagen "Referenzen können nicht const sein, weil sie immer const sind"?
Ruakh
2
Es mag sein, dass " semantisch alle Aktionen auf sie, nachdem sie gebunden wurden, auf das Objekt übertragen werden, auf das sie sich beziehen", aber das OP hatte erwartet, dass dies auch für sie gilt decltype, und stellte fest, dass dies nicht der Fall war .
Ruakh
9
Es gibt hier eine Menge mäanderförmiger Vermutungen und zweifelhaft anwendbarer Analogien zu Zeigern, was das Problem meiner Meinung nach mehr als notwendig verwirrt, wenn die Überprüfung des Standards wie bei Sonyuanyao direkt zum eigentlichen Punkt führt: Eine Referenz ist daher kein Lebenslauf-qualifizierbarer Typ es kann nicht sein const, std::is_constmuss also zurückkehren false. Sie hätten stattdessen Formulierungen verwenden können, die bedeuteten, dass sie zurückkehren müssen true, aber sie taten es nicht. Das ist es! All dieses Zeug über Zeiger, "Ich nehme an", "Ich nehme an" usw. liefert keine wirkliche Erklärung.
underscore_d
1
@underscore_d Dies ist wahrscheinlich eine bessere Frage für den Chat als für den Kommentarbereich hier, aber haben Sie ein Beispiel für "unnötige Verwirrung" bei dieser Art, über Referenzen nachzudenken? Wenn sie auf diese Weise implementiert werden können, was ist das Problem, wenn man sie so betrachtet? (Ich denke nicht, dass diese Frage zählt, da decltypees sich nicht um eine Laufzeitoperation handelt. Daher trifft die Idee "Zur Laufzeit verhalten sich Referenzen wie Zeiger", ob korrekt oder nicht, nicht wirklich zu.
Kyle Strand
1
Referenzen werden auch nicht syntaktisch wie Zeiger behandelt. Sie haben unterschiedliche Syntax zu deklarieren und zu verwenden. Ich bin mir also nicht mal sicher, was du meinst.
Jordan Melo
54

warum ist "ri" nicht "const"?

std::is_const prüft, ob der Typ const-qualifiziert ist oder nicht.

Wenn T ein const-qualifizierter Typ ist (dh const oder const flüchtig), wird der Wert der Elementkonstante gleich true angegeben. Für jeden anderen Typ ist der Wert false.

Die Referenz kann jedoch nicht const-qualifiziert werden. Referenzen [dcl.ref] / 1

Lebenslaufqualifizierte Referenzen sind schlecht geformt, es sei denn, die Lebenslaufqualifizierer werden unter Verwendung eines typedef-Namens ([dcl.typedef], [temp.param]) oder eines decltype-Spezifizierers ([dcl.type.simple]) eingeführt. In diesem Fall werden die Lebenslaufqualifizierer ignoriert.

So is_const<decltype(ri)>::valuekehrt falsebecuase ri(die Referenz) ist kein const qualifizierten Typ. Wie Sie sagten, können wir eine Referenz nach der Initialisierung nicht erneut binden, was bedeutet, dass die Referenz immer "const" ist. Andererseits ist eine const-qualifizierte Referenz oder eine const-unqualifizierte Referenz möglicherweise nicht sinnvoll.

songyuanyao
quelle
4
Direkt zum Standard und damit direkt zum Punkt, anstatt die ganze Waffelannahme der akzeptierten Antwort.
underscore_d
5
@underscore_d Dies ist eine gute Antwort, bis Sie erkennen, dass die Operation auch gefragt hat, warum. "Weil es so heißt" ist für diesen Aspekt der Frage nicht wirklich nützlich.
simpleuser
5
@ user9999999 Die andere Antwort beantwortet diesen Aspekt auch nicht wirklich. Wenn eine Referenz nicht zurückgebunden werden kann, warum wird sie nicht is_constzurückgegeben true? Diese Antwort versucht, eine Analogie zu ziehen, wie Zeiger optional wieder eingesetzt werden können, wohingegen Referenzen dies nicht tun - und führt dabei aus demselben Grund zu Selbstwiderspruch. Ich bin mir nicht sicher, ob es eine echte Erklärung gibt, außer einer etwas willkürlichen Entscheidung derjenigen, die den Standard schreiben, und manchmal ist das das Beste, auf das wir hoffen können. Daher diese Antwort.
underscore_d
2
Ein weiterer wichtiger Aspekt, glaube ich, ist , dass decltypeist keine Funktion und daher direkt auf den Nachschlagewerken selbst und nicht auf die genannten zu Objekt. (Dies ist vielleicht relevanter für die Antwort "Referenzen sind im Grunde Zeiger", aber ich denke immer noch, dass es Teil dessen ist, was dieses Beispiel verwirrend und daher erwähnenswert macht.)
Kyle Strand
3
Um den Kommentar von @KyleStrand weiter zu erläutern: verhält sich decltype(name)anders als ein General decltype(expr). So ist zum Beispiel decltype(i)der deklarierte Typ, der iist const int, während decltype((i))wäre int const &.
Daniel Schepler
18

Sie müssen verwenden, std::remove_referenceum den Wert zu erhalten, den Sie suchen.

std::cout << std::is_const<std::remove_reference<decltype(ri)>::type>::value << std::endl;

Weitere Informationen finden Sie in diesem Beitrag .

chema989
quelle
7

Warum sind Makros nicht const? Funktionen? Literale? Die Namen der Typen?

const Dinge sind nur eine Teilmenge unveränderlicher Dinge.

Da Referenztypen genau das sind - Typen -, mag es sinnvoll gewesen sein, den constQualifizierer für alle für die Symmetrie mit anderen Typen (insbesondere mit Zeigertypen) zu benötigen , aber dies würde sehr schnell sehr mühsam werden.

Wenn C ++ standardmäßig unveränderliche Objekte hätte und das mutableSchlüsselwort für alles benötigt, was Sie nicht sein wollten const, wäre dies einfach gewesen: Erlauben Sie Programmierern einfach nicht mutable, Referenztypen hinzuzufügen .

So wie es ist, sind sie ohne Qualifikation unveränderlich.

Und da sie nicht sind , constQualifiziert, dann wäre es wohl mehr für verwirrende is_constArt auf einer Referenz treu zu ergeben.

Ich halte dies für einen vernünftigen Kompromiss, zumal die Unveränderlichkeit ohnehin durch die bloße Tatsache erzwungen wird, dass keine Syntax zum Mutieren einer Referenz existiert.

Leichtigkeitsrennen im Orbit
quelle
5

Dies ist eine Besonderheit in C ++. Obwohl wir Referenzen nicht als Typen betrachten, "sitzen" sie tatsächlich im Typensystem. Obwohl dies umständlich erscheint (da bei Verwendung von Referenzen die Referenzsemantik automatisch erfolgt und die Referenz "aus dem Weg geht"), gibt es einige vertretbare Gründe, warum Referenzen im Typsystem anstatt als separates Attribut außerhalb von modelliert werden Art.

Betrachten wir zunächst, dass nicht jedes Attribut eines deklarierten Namens im Typsystem enthalten sein muss. Aus der C-Sprache haben wir "Speicherklasse" und "Verknüpfung". Ein Name kann als eingeführt werden extern const int ri, wobei dies die externstatische Speicherklasse und das Vorhandensein einer Verknüpfung angibt. Der Typ ist einfach const int.

C ++ umfasst offensichtlich die Vorstellung, dass Ausdrücke Attribute haben, die außerhalb des Typsystems liegen. Die Sprache hat jetzt ein Konzept der "Wertklasse", bei dem versucht wird, die wachsende Anzahl von Nicht-Typ-Attributen zu organisieren, die ein Ausdruck aufweisen kann.

Referenzen sind jedoch Typen. Warum?

Es wird verwendet , um in C ++ Anleitungen erklärt wird , dass eine Erklärung , wie const int &rieingeführt , riwie mit Typ const int, aber Referenzsemantik. Diese Referenzsemantik war kein Typ; Es war einfach eine Art Attribut, das auf eine ungewöhnliche Beziehung zwischen dem Namen und dem Speicherort hinweist. Darüber hinaus wurde die Tatsache, dass Referenzen keine Typen sind, verwendet, um zu erklären, warum Sie keine Typen basierend auf Referenzen erstellen können, obwohl die Typkonstruktionssyntax dies zulässt. Zum Beispiel sind Arrays oder Zeiger auf Referenzen nicht möglich: const int &ari[5]und const int &*pri.

Tatsächlich sind Referenzen jedoch Typen und rufen so decltype(ri)einen Knoten vom Referenztyp ab, der nicht qualifiziert ist. Sie müssen im Typbaum über diesen Knoten hinaus absteigen, um zum zugrunde liegenden Typ mit zu gelangen remove_reference.

Wenn Sie verwenden ri, wird die Referenz transparent aufgelöst, sodass ri"aussieht und sich anfühlt i" und als "Alias" dafür bezeichnet werden kann. Im Typsystem gibt es rijedoch tatsächlich einen Typ, der " Verweis auf const int " ist.

Warum sind Referenztypen?

Wenn Referenzen keine Typen wären, würden diese Funktionen denselben Typ haben:

void foo(int);
void foo(int &);

Das kann einfach nicht aus Gründen sein, die ziemlich selbstverständlich sind. Wenn sie den gleichen Typ hätten, bedeutet dies, dass jede Deklaration für jede Definition geeignet wäre und daher jede (int)Funktion verdächtigt werden müsste, eine Referenz zu nehmen.

Wenn Referenzen keine Typen wären, wären diese beiden Klassendeklarationen gleichwertig:

class foo {
  int m;
};

class foo {
  int &m;
};

Es wäre richtig, wenn eine Übersetzungseinheit eine Deklaration und eine andere Übersetzungseinheit im selben Programm die andere Deklaration verwenden würde.

Tatsache ist, dass eine Referenz einen Unterschied in der Implementierung impliziert und es unmöglich ist, diesen vom Typ zu trennen, da der Typ in C ++ mit der Implementierung einer Entität zu tun hat: ihr "Layout" in Bits sozusagen. Wenn zwei Funktionen denselben Typ haben, können sie mit denselben Konventionen für binäre Aufrufe aufgerufen werden: Der ABI ist derselbe. Wenn zwei Strukturen oder Klassen denselben Typ haben, ist ihr Layout und die Semantik des Zugriffs auf alle Mitglieder identisch. Das Vorhandensein von Referenzen ändert diese Aspekte von Typen. Daher ist es eine einfache Entwurfsentscheidung, sie in das Typsystem zu integrieren. (Beachten Sie hier jedoch ein Gegenargument: Es kann ein Struktur- / Klassenmitglied sein static, das auch die Darstellung ändert; dies ist jedoch kein Typ!)

Daher sind Referenzen im Typensystem als "Bürger zweiter Klasse" (ähnlich wie Funktionen und Arrays in ISO C). Es gibt bestimmte Dinge, die wir mit Referenzen nicht "tun" können, z. B. das Deklarieren von Zeigern auf Referenzen oder Arrays davon. Das heißt aber nicht, dass sie keine Typen sind. Sie sind einfach keine Typen in einer Weise, die Sinn macht.

Nicht alle diese Einschränkungen zweiter Klasse sind wesentlich. Da es Referenzstrukturen gibt, kann es auch Referenzarrays geben! Z.B

// fantasy syntax
int x = 0, y = 0;
int &ar[2] = { x, y };

// ar[0] is now an alias for x: could be useful!

Dies ist einfach nicht in C ++ implementiert, das ist alles. Zeiger auf Referenzen sind jedoch überhaupt nicht sinnvoll, da ein aus einer Referenz entfernter Zeiger nur auf das referenzierte Objekt verweist. Der wahrscheinliche Grund, warum es keine Arrays von Referenzen gibt, ist, dass die C ++ - Leute Arrays als eine Art Low-Level-Feature betrachten, das von C geerbt wurde und in vielerlei Hinsicht fehlerhaft und irreparabel ist, und dass sie Arrays nicht als das berühren möchten Basis für etwas Neues. Das Vorhandensein von Referenzarrays wäre jedoch ein klares Beispiel dafür, wie Referenzen Typen sein müssen.

Nicht constqualifizierbare Typen: auch in ISO C90 enthalten!

Einige Antworten deuten darauf hin, dass Referenzen kein constQualifikationsmerkmal haben. Das ist eher ein roter Hering, weil die Deklaration const int &ri = inicht einmal versucht , eine constqualifizierte Referenz zu erstellen: Es ist eine Referenz auf einen const-qualifizierten Typ (was selbst nicht der Fall ist const). Genau wie const in *rideklariert ein Zeiger auf etwas const, aber dieser Zeiger ist selbst nicht const.

Es ist jedoch richtig, dass Referenzen das constQualifikationsmerkmal nicht selbst tragen können.

Dies ist jedoch nicht so bizarr. Selbst in der Sprache ISO C 90 können nicht alle Typen sein const. Arrays können nämlich nicht sein.

Erstens existiert die Syntax zum Deklarieren eines const-Arrays nicht: int a const [42]ist fehlerhaft.

Was die obige Erklärung zu tun versucht, kann jedoch über ein Zwischenprodukt ausgedrückt werden typedef:

typedef int array_t[42];
const array_t a;

Aber das macht nicht so, wie es aussieht. In dieser Erklärung ist es nicht , adas bekommt constqualifiziert, aber die Elemente! Das heißt, a[0]ist ein const int, ist aber anur "Array von int". Folglich ist hierfür keine Diagnose erforderlich:

int *p = a; /* surprise! */

Das macht:

a[0] = 1;

Dies unterstreicht erneut die Idee, dass Referenzen im Typsystem in gewisser Weise "zweite Klasse" sind, wie Arrays.

Beachten Sie, wie tief die Analogie gilt, da Arrays wie Referenzen auch ein "unsichtbares Konvertierungsverhalten" aufweisen. Ohne dass der Programmierer einen expliziten Operator verwenden muss, wird der Bezeichner aautomatisch zu einem int *Zeiger, als ob der Ausdruck verwendet &a[0]worden wäre. Dies ist analog dazu, wie eine Referenz ri, wenn wir sie als primären Ausdruck verwenden, das Objekt, ian das sie gebunden ist , auf magische Weise bezeichnet . Es ist nur ein weiterer "Zerfall" wie der "Zerfall von Array zu Zeiger".

Und so wie wir nicht durch den Zerfall von "Array zu Zeiger" in den falschen Gedanken verwirrt werden dürfen, dass "Arrays nur Zeiger in C und C ++ sind", dürfen wir auch nicht denken, dass Referenzen nur Aliase sind, die keinen eigenen Typ haben.

Wenn decltype(ri)die übliche Konvertierung der Referenz in ihr Referenzobjekt sizeof aunterdrückt wird, unterscheidet sich dies nicht wesentlich von der Unterdrückung der Array-zu-Zeiger-Konvertierung und der Bearbeitung des Array-Typs selbst zur Berechnung seiner Größe.

Kaz
quelle
Sie haben hier viele interessante und hilfreiche Informationen (+1), aber diese beschränken sich mehr oder weniger auf die Tatsache, dass "Referenzen im Typensystem sind" - dies beantwortet die Frage von OP nicht vollständig. Zwischen dieser Antwort und der Antwort von @ songyuanyao würde ich sagen, dass es mehr als genug Informationen gibt, um die Situation zu verstehen, aber es scheint eher der notwendige Hintergrund für eine Antwort als eine vollständige Antwort zu sein.
Kyle Strand
1
Ich denke auch, dass dieser Satz hervorzuheben ist: "Wenn Sie ri verwenden, wird die Referenz transparent aufgelöst ..." Ein wichtiger Punkt (den ich in Kommentaren erwähnt habe, der aber bisher in keiner der Antworten aufgetaucht ist ) ist , dass decltype nicht der Fall ist diese transparente Auflösung durchführen (es ist keine Funktion, so riwird nicht „gebraucht“ im Sinne Sie beschreiben). Dies passt sehr gut zu Ihrem gesamten Fokus auf das Typsystem - die Schlüsselverbindung ist, dass decltypees sich um eine Typsystemoperation handelt .
Kyle Strand
Hmm, das Zeug über Arrays fühlt sich wie eine Tangente an, aber der letzte Satz ist hilfreich.
Kyle Strand
..... obwohl ich Sie zu diesem Zeitpunkt bereits positiv bewertet habe und nicht der OP bin, so dass Sie nichts wirklich gewinnen oder verlieren, wenn Sie auf meine Kritik hören ....
Kyle Strand
Natürlich weist uns eine einfache (und richtige) Antwort nur auf den Standard hin, aber ich mag das Hintergrunddenken hier, obwohl einige argumentieren könnten, dass Teile davon Vermutungen sind. +1.
Steve Kidd
-5

const X & x ”bedeutet, dass x ein X-Objekt aliasisiert, aber Sie können dieses X-Objekt nicht über x ändern.

Und siehe std :: is_const .

Sotter.liu
quelle
Dies versucht nicht einmal, die Frage zu beantworten.
Bolov