Ich habe C++ Primer gelesen und konnte nicht ganz verstehen, wann ein Ausdruck einen Objekttyp und wann einen Referenztyp für das Objekt liefert.
Ich zitiere aus dem Buch:
- Wenn wir decltype auf einen Ausdruck anwenden, der keine Variable ist, erhalten wir den Typ, den > dieser Ausdruck ergibt.
- Generell gibt decltype einen Referenztyp für Ausdrücke zurück, die Objekte liefern, die auf der linken Seite der Zuweisung stehen können.
Unter Berücksichtigung des folgenden Codes:
int i = 3, *ptr = &i, &ref = i;
decltype(ref + 0) j;
Im obigen Code führt der Ausdruck “ref + 0” zu einer inhärenten Operation der Addition des Werts des Objekts, auf das sich ref bezieht, i und 0. Daher ergibt der Ausdruck gemäß der ersten Regel einen int-Typ. Aber nach der zweiten Regel, da der Ausdruck den Typ eines Objekts liefert, das auf der linken Seite einer Zuweisung stehen kann (in diesem Fall int), sollte der decltype nicht eine Referenz auf den Typ int(int&) liefern?
Das Buch sagt auch, für den folgenden Code
decltype(*ptr) k;
k hat den Typ int& und nicht int, den Typ, den der Ausdruck ergibt.
Es sagt das auch für einen Zuweisungsausdruck wie im folgenden Code
decltype(a = b) l;
l hätte die Art der Referenz auf das Objekt auf der linken Seite der Zuweisungsoperation.
Woher wissen wir, welche Ausdrücke den Objekttyp und welche die Referenz auf den Objekttyp liefern?
Es ist nicht einfach, diese Konzepte zu verstehen, ohne formal zu werden. Die Fibel will Sie wahrscheinlich nicht verwirren und vermeidet die Einführung von Begriffen wie „Wert“, “rwert“, und “xWert“. Leider sind diese grundlegend, um zu verstehen, wie decltype
funktioniert.
Zunächst einmal ist der Typ eines ausgewerteten Ausdrucks niemals ein Referenztyp oder ein Typ der obersten Ebene const
-qualifizierter Typ für Nicht-Klassen-Typen (zB int const
oder int&
). Wenn sich herausstellt, dass der Typ eines Ausdrucks ist int&
oder int const
es wird sofort umgewandelt in int
vor jeder weiteren Auswertung.
Dies ist in den Absätzen 5/5 und 5/6 des C++11-Standards angegeben:
5 Hat ein Ausdruck zunächst den Typ „Referenz auf T“ (8.3.2, 8.5.3), wird der Typ angepasst T
vor jeder weiteren Analyse. Der Ausdruck bezeichnet das durch die Referenz bezeichnete Objekt oder die Funktion, und der Ausdruck ist ein Wert oder ein xWertje nach Ausdruck.
6 Wenn a Prwert hat zunächst den Typ „cv T“, wobei T
ist ein CV-unqualifizierter Nicht-Klassen-, Nicht-Array-Typ, an den der Typ des Ausdrucks angepasst ist T
vor jeder weiteren Analyse.
So viel zu Ausdrücken. Was macht decltype
tun? Nun, die Regeln, die das Ergebnis bestimmen decltype(e)
für einen bestimmten Ausdruck e
sind in Absatz 7.1.6.2/4 angegeben:
Der mit bezeichnete Typ decltype(e)
ist wie folgt definiert:
– wenn e
ist eine ungeklammerte ID-Ausdruck oder ein Klassenmitgliedszugriff ohne Klammern (5.2.5), decltype(e)
ist der Typ der benannten Entität e
. Wenn es keine solche Entität gibt, oder wenn e
einen Satz überladener Funktionen benennt, ist das Programm schlecht formatiert;
– sonst, wenn e
ist ein xWert, decltype(e)
ist T&&
wo T
ist die Art von e
;
– sonst, wenn e
ist ein Wert, decltype(e)
ist T&
wo T
ist die Art von e
;
– ansonsten, decltype(e)
ist die Art von e
.
Der Operand der decltype
Bezeichner ist ein nicht ausgewerteter Operand (Klausel 5).
Das kann in der Tat verwirrend klingen. Versuchen wir, es Stück für Stück zu analysieren. Zunächst:
– wenn e
ist eine ungeklammerte ID-Ausdruck oder ein Klassenmitgliedszugriff ohne Klammern (5.2.5), decltype(e)
ist der Typ der benannten Entität e
. Wenn es keine solche Entität gibt, oder wenn e
einen Satz überladener Funktionen benennt, ist das Programm schlecht formatiert;
Das ist einfach. Wenn e
ist nur der Name einer Variablen und Sie setzen ihn nicht in Klammern, dann ist das Ergebnis von decltype
ist der Typ dieser Variablen. So
bool b; // decltype(b) = bool
int x; // decltype(x) = int
int& y = x; // decltype(y) = int&
int const& z = y; // decltype(z) = int const&
int const t = 42; // decltype
Beachten Sie, dass das Ergebnis von decltype(e)
Hier ist nicht unbedingt derselbe wie der Typ des ausgewerteten Ausdrucks e
. Zum Beispiel die Auswertung des Ausdrucks z
ergibt einen Wert vom Typ int const
nicht int const&
(weil nach Absatz 5/5 die &
abgezogen wird, wie wir zuvor gesehen haben).
Mal sehen, was passiert, wenn der Ausdruck nicht nur ein Bezeichner ist:
– sonst, wenn e
ist ein xWert, decltype(e)
ist T&&
wo T
ist die Art von e
;
Das wird kompliziert. Was ist ein xWert? Grundsätzlich ist es eine der drei Kategorien, zu denen ein Ausdruck gehören kann (xWert, Wertoder Prwert). Ein xWert wird normalerweise erhalten, wenn eine Funktion mit einem Rückgabetyp aufgerufen wird, der an ist rwert Referenztyp oder als Ergebnis einer statischen Umwandlung in eine rwert Referenztyp. Das typische Beispiel ist ein Aufruf an std::move()
.
Um den Wortlaut aus dem Standard zu verwenden:
[ Note: An expression is an xvalue if it is:
— the result of calling a function, whether implicitly or explicitly, whose return type is an rvalue reference
to object type,
— a cast to an rvalue reference to object type,
— a class member access expression designating a non-static data member of non-reference type in which
the object expression is an xvalue, or
— a .*
pointer-to-member expression in which the first operand is an xvalue and the second operand is
a pointer to data member.
In general, the effect of this rule is that named rvalue references are treated as lvalues and unnamed rvalue
references to objects are treated as xvalues; rvalue references to functions are treated as lvalues whether
named or not. —end note ]
Also zum Beispiel die Ausdrücke std::move(x)
, static_cast<int&&>(x)
und std::move(p).first
(für ein Objekt p
des Typs pair
) sind xvalues. Wenn Sie sich bewerben decltype
zu einem xWert Ausdruck, decltype
anhängt &&
zum Typ des Ausdrucks:
int x; // decltype(std::move(x)) = int&&
// decltype(static_cast<int&&>(x)) = int&&
Lass uns weitermachen:
– sonst, wenn e
ist ein Wert, decltype(e)
ist T&
wo T
ist die Art von e
;
Was ist ein Wert? Nun, informell, Wert Ausdruck sind Ausdrücke, die Objekte bezeichnen, auf die in Ihrem Programm wiederholt verwiesen werden kann – zum Beispiel Variablen mit einem Namen und/oder Objekte, deren Adresse Sie übernehmen können.
Für einen Ausdruck e
des Typs T
das ist ein Wert Ausdruck, decltype(e)
Erträge T&
. Also zum Beispiel:
int x; // decltype(x) = int (as we have seen)
// decltype((x)) = int& - here the expression is parenthesized, so the
// first bullet does not apply and decltype appends & to the type of
// the expression (x), which is int
Ein Funktionsaufruf für eine Funktion, deren Rückgabetyp ist T&
ist auch ein Wert Ausdruck, also:
int& foo() { return x; } // decltype(foo()) = int&
Endlich:
– ansonsten, decltype(e)
ist die Art von e
.
Wenn der Ausdruck keine ist xWert noch ein Wert (mit anderen Worten, wenn es sich um eine Prwert), das Ergebnis von decltype(e)
ist einfach die Art von e
. Unbenannte Temporäre und Literale sind PrWerte. Also zum Beispiel:
int foo() { return x; } // Function calls for functions that do not return
// a reference type are prvalue expressions
// decltype(foo()) = int
// decltype(42) = int
Wenden wir das Obige auf die Beispiele aus Ihrer Frage an. Angesichts dieser Erklärungen:
int i = 3, *ptr = &i, &ref = i;
decltype(ref + 0) j;
decltype(*ptr) k;
decltype(a = b) l;
Die Art von j
wird sein int
weil operator +
gibt a zurück Prwert des Typs int
. Die Art von k
wird sein int&
weil die unäre operator *
ergibt ein Wert (siehe Abschnitt 5.3.1/1). Die Art von l
ist auch int&
weil das Ergebnis von operator =
ist ein Wert (siehe Abschnitt 5.17/1).
Zu diesem Teil Ihrer Frage:
Aber nach der zweiten Regel, da der Ausdruck den Typ eines Objekts liefert, das auf der linken Seite einer Zuweisung stehen kann (in diesem Fall int), sollte der decltype nicht eine Referenz auf den Typ int(int&) liefern?
Sie haben diese Passage aus dem Buch wahrscheinlich falsch interpretiert. Nicht alle Objekte des Typs int
kann sich auf der linken Seite einer Zuweisung befinden. Die folgende Zuweisung ist beispielsweise illegal:
int foo() { return 42; }
foo() = 24; // ERROR! foo() is a prvalue expression, cannot be on the left
// side of an assignment
Ob ein Ausdruck auf der linken Seite einer Zuweisung erscheinen kann oder nicht (beachten Sie, dass wir über die sprechen eingebaut Zuweisungsoperator für grundlegende Datentypen hier) hängt von der ab Wert Kategorie dieses Ausdrucks (Wert, xWertoder Prwert), und die Wertekategorie eines Ausdrucks ist unabhängig von seinem Typ.
Für Ausdrücke, wie in Ihren Beispielen, stellt decltype einen Referenztyp bereit, wenn das Argument lvalue ist.
7.1.6.2p4:
The type denoted by decltype(e) is defined as follows:
— if e is an unparenthesized id-expression or an unparenthesized class member access (5.2.5), decltype(e) is the type of the entity named by e. If there is no such entity, or if e names a set of overloaded functions, the program is ill-formed;
— otherwise, if e is an xvalue, decltype(e) is T&&, where T is the type of e;
— otherwise, if e is an lvalue, decltype(e) is T&, where T is the type of e;
— otherwise, decltype(e) is the type of e.
The operand of the decltype specifier is an unevaluated operand (Clause 5).
[ Example:
const int&& foo();
int i;
struct A { double x; };
const A* a = new A();
decltype(foo()) x1 = i; // type is const int&&
decltype(i) x2; // type is int
decltype(a->x) x3; // type is double
decltype((a->x)) x4 = x3; // type is const double&
—end example ]
.