it-swarm.com.de

Was sind rWerte, lWerte, xWerte, glWerte und prWerte?

In C++ 03 ist ein Ausdruck entweder ein Wert oder ein Wert .

In C++ 11 kann ein Ausdruck sein:

  1. rWert
  2. lWert
  3. xvalue
  4. glvalue
  5. prvalue

Aus zwei Kategorien sind fünf Kategorien geworden.

  • Was sind diese neuen Kategorien von Ausdrücken?
  • In welcher Beziehung stehen diese neuen Kategorien zu den vorhandenen Kategorien rvalue und lvalue?
  • Stimmen die Kategorien rvalue und lvalue in C++ 0x mit denen in C++ 03 überein?
  • Warum werden diese neuen Kategorien benötigt? Versuchen die WG21 Götter nur, uns Sterbliche zu verwirren?
1273
James McNellis

Ich denke, dieses Dokument könnte als nicht ganz so kurze Einführung dienen: n3055

Das ganze Massaker begann mit der Bewegungssemantik. Sobald wir Ausdrücke haben, die verschoben und nicht kopiert werden können, erforderten plötzlich leicht verständliche Regeln eine Unterscheidung zwischen Ausdrücken, die verschoben werden können, und in welche Richtung.

Nach dem, was ich auf der Grundlage des Entwurfs vermute, bleibt die R/L-Wertunterscheidung gleich, nur im Kontext von sich bewegenden Dingen wird es chaotisch.

Werden sie gebraucht Wahrscheinlich nicht, wenn wir auf die neuen Funktionen verzichten wollen. Aber um eine bessere Optimierung zu ermöglichen, sollten wir sie wahrscheinlich annehmen.

Zitiert n3055 :

  • Ein l-Wert (historisch gesehen so genannt, weil l-Werte auf der linken Seite eines Zuweisungsausdrucks stehen könnten) bezeichnet eine Funktion oder ein Objekt. [Beispiel: Wenn E ein Ausdruck vom Zeigertyp ist, dann ist *E ein Wertausdruck, der sich auf das Objekt oder die Funktion bezieht, auf das/die E verweist. Als weiteres Beispiel ist das Ergebnis des Aufrufs einer Funktion, deren Rückgabetyp eine lWert-Referenz ist, ein lWert.]
  • Ein x-Wert (ein "eXpiring" -Wert) bezieht sich auch auf ein Objekt, normalerweise gegen Ende seiner Lebensdauer (damit seine Ressourcen verschoben werden können, z Beispiel). Ein x-Wert ist das Ergebnis bestimmter Arten von Ausdrücken, die r-Wert-Referenzen enthalten. [Beispiel: Das Ergebnis des Aufrufs einer Funktion, deren Rückgabetyp eine r-Wert-Referenz ist, ist ein x-Wert.]
  • Ein glvalue ("generalisierter" lvalue) ist ein lvalue oder ein xvalue .
  • Ein r-Wert (historisch gesehen so genannt, weil r-Werte auf der rechten Seite eines Zuweisungsausdrucks erscheinen könnten) ist ein x-Wert, ein temporäres Objekt oder Unterobjekt davon oder ein Wert, der keinem Objekt zugeordnet ist.
  • A prvalue (“pure” rvalue) is an rvalue that is not an xvalue. [Example: The result of calling a function whose return type is not a reference is a prvalue]

Das betreffende Dokument ist eine hervorragende Referenz für diese Frage, da es die genauen Änderungen in der Norm zeigt, die infolge der Einführung der neuen Nomenklatur eingetreten sind.

596

Was sind diese neuen Kategorien von Ausdrücken?

Die FCD (n3092) hat eine hervorragende Beschreibung:

- Ein lWert (historisch gesehen so genannt, weil lWerte auf der linken Seite eines Zuweisungsausdrucks stehen können) bezeichnet eine Funktion oder ein Objekt. [Beispiel: Wenn E ein Ausdruck vom Zeigertyp ist, dann ist * E ein Wertausdruck, der sich auf das Objekt oder die Funktion bezieht, auf die E zeigt. Als weiteres Beispiel ist das Ergebnis des Aufrufs einer Funktion, deren Rückgabetyp eine lWert-Referenz ist, ein lWert. —End Beispiel]

- Ein x-Wert (ein "eXpiring" -Wert) bezieht sich auch auf ein Objekt, normalerweise gegen Ende seiner Lebensdauer (damit beispielsweise seine Ressourcen verschoben werden können). Ein x-Wert ist das Ergebnis bestimmter Ausdrücke mit r-Wert-Referenzen (8.3.2). [Beispiel: Das Ergebnis des Aufrufs einer Funktion, deren Rückgabetyp eine rWert-Referenz ist, ist ein xWert. —End Beispiel]

- Ein glvalue ("generalisierter" lvalue) ist ein lvalue oder ein xvalue.

- Ein r-Wert (historisch gesehen so genannt, weil r-Werte auf der rechten Seite eines Zuweisungsausdrucks erscheinen könnten) ist ein x-Wert, ein temporäres Objekt (12.2) oder ein Unterobjekt davon oder ein Wert, der keinem Objekt zugeordnet ist.

- Ein prvalue ("reiner" rvalue) ist ein rvalue, der kein xvalue ist. [Beispiel: Das Ergebnis des Aufrufs einer Funktion, deren Rückgabetyp keine Referenz ist, ist ein Wert. Der Wert eines Literal wie 12, 7.3e5 oder true ist ebenfalls ein Wert. —End Beispiel]

Jeder Ausdruck gehört zu genau einer der grundlegenden Klassifikationen in dieser Taxonomie: Ivalue, Xvalue oder Prvalue. Diese Eigenschaft eines Ausdrucks wird als Wertkategorie bezeichnet. [Anmerkung: Die Beschreibung jedes eingebauten Operators in Abschnitt 5 gibt die Kategorie des Werts an, den er liefert, und die Wertkategorien der Operanden, die er erwartet. Beispielsweise erwarten die integrierten Zuweisungsoperatoren, dass der linke Operand ein I-Wert und der rechte Operand ein Pr-Wert ist, und ergeben als Ergebnis einen I-Wert. Benutzerdefinierte Operatoren sind Funktionen, und die Kategorien von Werten, die sie erwarten und liefern, werden durch ihre Parameter und Rückgabetypen bestimmt. - Ende der Notiz

Ich schlage vor, Sie lesen den gesamten Abschnitt 3.10 L-Werte und r-Werte .

In welcher Beziehung stehen diese neuen Kategorien zu den vorhandenen Kategorien rvalue und lvalue?

Nochmal:

Taxonomy

Stimmen die Kategorien rvalue und lvalue in C++ 0x mit denen in C++ 03 überein?

Die Semantik der Werte hat sich insbesondere mit der Einführung der Bewegungssemantik weiterentwickelt.

Warum werden diese neuen Kategorien benötigt?

Damit konnte die Umzugskonstruktion/-zuordnung definiert und unterstützt werden.

325
dirkgently

Ich werde mit Ihrer letzten Frage beginnen:

Warum werden diese neuen Kategorien benötigt?

Der C++ - Standard enthält viele Regeln, die sich mit der Wertkategorie eines Ausdrucks befassen. Einige Regeln unterscheiden zwischen lvalue und rvalue. Zum Beispiel, wenn es um Überlastungsauflösung geht. Andere Regeln unterscheiden zwischen glvalue und prvalue. Sie können beispielsweise einen glWert mit einem unvollständigen oder abstrakten Typ haben, aber es gibt keinen prWert mit einem unvollständigen oder abstrakten Typ. Bevor wir diese Terminologie hatten, enthielten die Regeln, die tatsächlich zwischen glvalue/prvalue und lvalue/rvalue unterscheiden müssen, entweder unbeabsichtigt falsche oder viele Erklärungen und Ausnahmen von der Regel a la "... es sei denn, der rvalue ist auf unbenannte zurückzuführen rWertreferenz ... ". Es scheint also eine gute Idee zu sein, den Konzepten von glvalues ​​und prvalues ​​nur ihren eigenen Namen zu geben.

Was sind diese neuen Kategorien von Ausdrücken? In welcher Beziehung stehen diese neuen Kategorien zu den vorhandenen Kategorien rvalue und lvalue?

Wir haben immer noch die Begriffe lvalue und rvalue, die mit C++ 98 kompatibel sind. Wir haben die r-Werte in zwei Untergruppen unterteilt, x-Werte und pr-Werte, und wir bezeichnen l-Werte und x-Werte als gl-Werte. X-Werte sind eine neue Art von Wertkategorie für unbenannte R-Wert-Referenzen. Jeder Ausdruck ist einer dieser drei: Ivalue, Xvalue, Prvalue. Ein Venn-Diagramm würde folgendermaßen aussehen:

    ______ ______
   /      X      \
  /      / \      \
 |   l  | x |  pr  |
  \      \ /      /
   \______X______/
       gl    r

Beispiele mit Funktionen:

int   prvalue();
int&  lvalue();
int&& xvalue();

Vergessen Sie aber auch nicht, dass benannte rvalue-Referenzen lvalues ​​sind:

void foo(int&& t) {
  // t is initialized with an rvalue expression
  // but is actually an lvalue expression itself
}
175
sellibitze

Warum werden diese neuen Kategorien benötigt? Versuchen die WG21-Götter nur, uns Sterbliche zu verwirren?

Ich glaube nicht, dass die anderen Antworten (obwohl viele davon gut sind) die Antwort auf diese spezielle Frage wirklich erfassen. Ja, diese und ähnliche Kategorien ermöglichen die Bewegungssemantik, aber die Komplexität besteht aus einem Grund. Dies ist die einzige unantastbare Regel zum Verschieben von Inhalten in C++ 11:

Du sollst dich nur bewegen, wenn es ohne Zweifel sicher ist.

Deshalb gibt es diese Kategorien: um über Werte sprechen zu können, bei denen es sicher ist, sich von ihnen zu entfernen, und um über Werte zu sprechen, bei denen dies nicht der Fall ist.

In der frühesten Version von r-Wert-Referenzen war Bewegung kein Problem. Zu leicht. Leicht genug, dass es viel Potenzial gab, Dinge implizit zu bewegen, wenn der Benutzer es nicht wirklich wollte.

Hier sind die Umstände, unter denen es sicher ist, etwas zu bewegen:

  1. Wenn es sich um ein temporäres Objekt oder ein Unterobjekt handelt. (Wert)
  2. Wenn der Benutzer ausdrücklich gesagt hat, dass er es verschieben soll .

Wenn du das tust:

SomeType &&Func() { ... }

SomeType &&val = Func();
SomeType otherVal{val};

Was macht das? In älteren Versionen der Spezifikation würde dies eine Bewegung provozieren, bevor die 5 Werte eingingen. Natürlich tut es das. Sie haben eine rWert-Referenz an den Konstruktor übergeben, und daher wird sie an den Konstruktor gebunden, der eine rWert-Referenz verwendet. Das ist offensichtlich.

Es gibt nur ein Problem damit; Sie haben nicht darum gebeten , es zu verschieben. Oh, du könntest sagen, dass der && ein Hinweis sein sollte, aber das ändert nichts an der Tatsache, dass er die Regel gebrochen hat. val ist keine temporäre Datei, da temporäre Dateien keine Namen haben. Möglicherweise haben Sie die Lebensdauer des temporären Objekts verlängert, dies bedeutet jedoch, dass es nicht temporär ist . Es ist genau wie jede andere Stapelvariable.

Wenn es sich nicht um ein temporäres Objekt handelt und Sie nicht darum gebeten haben, es zu verschieben, ist das Verschieben falsch.

Die naheliegende Lösung besteht darin, val zu einem Wert zu machen. Das bedeutet, dass Sie sich nicht davon entfernen können. OK, gut; Es heißt, also ist es ein Wert.

Wenn Sie das getan haben, können Sie nicht mehr sagen, dass SomeType&& überall dasselbe bedeutet. Sie haben jetzt eine Unterscheidung zwischen benannten R-Wert-Referenzen und unbenannten R-Wert-Referenzen getroffen. Nun, benannte rvalue-Referenzen sind lvalues; das war unsere obige Lösung. Wie nennen wir also unbenannte rvalue-Referenzen (den Rückgabewert von Func oben)?

Es ist kein LWERT, weil man sich nicht von einem LWERT bewegen kann. Und wir brauchen , um uns bewegen zu können, indem wir einen && zurückgeben; wie könnte man sonst explizit sagen, etwas zu bewegen? Das ist es doch, was std::move zurückgibt. Es ist kein R-Wert (im alten Stil), da er sich auf der linken Seite einer Gleichung befinden kann (die Dinge sind tatsächlich etwas komplizierter, siehe diese Frage und die Kommentare unten). Es ist weder ein Wert noch ein Wert; Es ist eine neue Art von Sache.

Was wir haben, ist ein Wert, den Sie als Wert behandeln können , außer , von dem aus er implizit verschoben werden kann. Wir nennen es einen x-Wert.

Beachten Sie, dass wir durch x-Werte die beiden anderen Wertekategorien erhalten:

  • Ein prvalue ist eigentlich nur der neue Name für den vorherigen Typ von rvalue, d. H. Es handelt sich um die rvalues, bei denen keine xvalues ​​sind.

  • GlWerte sind die Vereinigung von xWerten und lWerten in einer Gruppe, da sie viele Eigenschaften gemeinsam haben.

Es kommt also wirklich auf die x-Werte und die Notwendigkeit an, die Bewegung auf genau und nur bestimmte Stellen zu beschränken. Diese Orte werden durch die rWert-Kategorie definiert. prvalues ​​sind die impliziten Bewegungen und xvalues ​​sind die expliziten Bewegungen (std::move gibt einen xvalue zurück).

151
Nicol Bolas

IMHO, die beste Erklärung über seine Bedeutung gab uns Stroustrup + berücksichtigen Beispiele von Dániel Sándor und Mohan :

Stroustrup:

Jetzt war ich ernsthaft besorgt. Offensichtlich waren wir auf dem Weg in eine Sackgasse oder ein Durcheinander oder beides. Ich habe die Mittagspause damit verbracht, eine Analyse durchzuführen, um festzustellen, welche der Eigenschaften (der Werte) unabhängig sind. Es gab nur zwei unabhängige Eigenschaften:

  • has identity - d. H. Und Adresse, ein Zeiger, der Benutzer kann bestimmen, ob zwei Kopien identisch sind, usw.
  • can be moved from - d. H. Wir dürfen die Quelle einer "Kopie" in einem unbestimmten, aber gültigen Zustand verlassen

Dies führte mich zu der Schlussfolgerung, dass es genau drei Arten von Werten gibt (unter Verwendung des Regex-Notationstricks, einen Großbuchstaben zu verwenden, um ein Negativ anzuzeigen - ich hatte es eilig):

  • iM: Hat Identität und kann nicht verschoben werden
  • im: Hat Identität und kann verschoben werden von (z. B. dem Ergebnis des Umsetzens eines Werts in eine R-Wert-Referenz)
  • Im: hat keine Identität und kann verschoben werden.

    Die vierte Möglichkeit, IM (hat keine Identität und kann nicht verschoben werden), ist in C++ (oder, glaube ich, in einer anderen Sprache) nicht nützlich.

Zusätzlich zu diesen drei grundlegenden Werteklassifikationen haben wir zwei offensichtliche Verallgemeinerungen, die den beiden unabhängigen Eigenschaften entsprechen:

  • i: hat Identität
  • m: kann von verschoben werden

Dies veranlasste mich, dieses Diagramm an die Tafel zu setzen: enter image description here

Benennung

Ich bemerkte, dass wir nur eingeschränkte Namensfreiheit hatten: Die beiden Punkte links (mit iM und i bezeichnet) sind das, was Leute mit mehr oder weniger Formalität lvalues genannt haben, und die beiden Punkte rechts (mit m und Im bezeichnet) haben Personen mit mehr oder weniger Formalität rvalues genannt. Dies muss sich in unserer Benennung widerspiegeln. Das heißt, das linke "Bein" des W sollte Namen haben, die mit lvalue zusammenhängen, und das rechte "Bein" des W sollte Namen haben, die mit rvalue. zusammenhängen dass sich diese ganze Diskussion/dieses Problem aus der Einführung von rWertreferenzen und der Bewegungssemantik ergibt. Diese Begriffe existieren in Stracheys Welt einfach nicht und bestehen nur aus rvalues und lvalues. Jemand hat beobachtet, dass die Ideen das

  • Jedes value ist entweder ein lvalue oder ein rvalue
  • Ein lvalue ist kein rvalue und ein rvalue ist kein lvalue

sind tief in unser Bewusstsein eingebettet, sehr nützliche Eigenschaften, und Spuren dieser Dichotomie finden sich überall im Normentwurf. Wir waren uns alle einig, dass wir diese Eigenschaften erhalten (und präzisieren) sollten. Dies schränkte unsere Namensauswahl weiter ein. Ich bemerkte, dass der Standardtext der Bibliothek rvalue bedeutet m (die Verallgemeinerung), so dass der rechte untere Punkt des W die Erwartung und den Text der Standardbibliothek bewahrt. sollte rvalue. heißen

Dies führte zu einer gezielten Diskussion der Benennung. Zuerst mussten wir uns für lvalue. entscheiden. Sollte lvalueiM bedeuten oder für die Verallgemeinerung i? Unter der Leitung von Doug Gregor haben wir die Stellen in der Kernsprache aufgelistet, an denen das Wort lvalue für das eine oder andere qualifiziert war. Eine Liste wurde erstellt und in den meisten Fällen und im schwierigsten/sprödesten Text bedeutet lvalue derzeit iM. Dies ist die klassische Bedeutung von lvalue, weil "in den alten Tagen" nichts bewegt wurde; move ist ein neuartiger Begriff in C++0x. Wenn Sie den oberen linken Punkt von Wlvalue benennen, erhalten Sie die Eigenschaft, dass jeder Wert ein lvalue oder ein rvalue ist, jedoch nicht beides.

Der obere linke Punkt von W ist lvalue und der untere rechte Punkt ist rvalue. Was bedeutet das für den unteren linken und den oberen rechten Punkt? Der untere linke Punkt ist eine Verallgemeinerung des klassischen L-Wertes, die eine Bewegung zulässt. Es ist also ein generalized lvalue. Wir haben es glvalue. genannt. Sie können über die Abkürzung streiten, aber (glaube ich) nicht mit der Logik. Wir gingen davon aus, dass bei ernsthaftem Gebrauch generalized lvalue irgendwie sowieso abgekürzt werden würde, also sollten wir es sofort tun (oder es besteht die Gefahr von Verwirrung). Der obere rechte Punkt des W ist weniger allgemein als der untere rechte (wird jetzt wie immer rvalue genannt). Dieser Punkt repräsentiert den ursprünglichen reinen Begriff eines Objekts, von dem aus Sie sich bewegen können, da es nicht erneut referenziert werden kann (außer von einem Destruktor). Ich mochte die Phrase specialized rvalue im Gegensatz zu generalized lvalue, aber pure rvalue, abgekürzt als prvalue, siegte (und wahrscheinlich zu Recht). Das linke Bein des W ist also lvalue und glvalue und das rechte Bein ist prvalue und rvalue. Übrigens ist jeder Wert entweder ein Wert oder ein Wert, aber nicht beide.

Dies lässt die obere Mitte des W: im; Das heißt, Werte, die Identität haben und verschoben werden können. Wir haben wirklich nichts, was uns zu einem guten Namen für diese esoterischen Bestien führt. Sie sind wichtig für Menschen, die mit dem (Entwurfs-) Standardtext arbeiten, werden aber wahrscheinlich nicht zu einem bekannten Namen. Wir haben keine wirklichen Einschränkungen in Bezug auf die Benennung gefunden, die uns leiten könnten. Deshalb haben wir "x" für das Zentrum, das Unbekannte, das Fremde, das Nur-Xpert oder sogar für die X-Bewertung ausgewählt.

Steve showing off the final product

123
Ivan Kush

EINFÜHRUNG

ISOC++ 11 (offiziell ISO/IEC 14882: 2011) ist die neueste Version des Standards der Programmiersprache C++. Es enthält einige neue Funktionen und Konzepte, zum Beispiel:

  • rWertreferenzen
  • xvalue, glvalue, prvalue Ausdruckswertkategorien
  • semantik verschieben

Wenn wir die Konzepte der neuen Ausdruckswertkategorien verstehen möchten, müssen wir uns bewusst sein, dass es r- und l-Wertereferenzen gibt. Es ist besser zu wissen, dass rWerte an nicht konstante rWertreferenzen übergeben werden können.

int& r_i=7; // compile error
int&& rr_i=7; // OK

Wir können uns ein Bild von den Konzepten der Wertkategorien machen, wenn wir den Unterabschnitt Lvalues ​​and rvalues ​​aus dem Arbeitsentwurf N3337 zitieren (der dem veröffentlichten ISOC++ 11-Standard am ähnlichsten ist).

3.10 L-Werte und r-Werte [basic.lval]

1 Ausdrücke werden gemäß der Taxonomie in Abbildung 1 kategorisiert.

  • Ein lWert (historisch gesehen so genannt, weil lWerte auf der linken Seite eines Zuweisungsausdrucks stehen können) bezeichnet eine Funktion oder ein Objekt. [Beispiel: Wenn E ein Ausdruck vom Zeigertyp ist, dann ist * E ein Wertausdruck, der sich auf das Objekt oder die Funktion bezieht, auf die E zeigt. Als weiteres Beispiel ist das Ergebnis des Aufrufs einer Funktion, deren Rückgabetyp eine lWert-Referenz ist, ein lWert. —End Beispiel]
  • Ein x-Wert (ein "eXpiring" -Wert) bezieht sich auch auf ein Objekt, normalerweise gegen Ende seiner Lebensdauer (damit beispielsweise seine Ressourcen verschoben werden können). Ein x-Wert ist das Ergebnis bestimmter Ausdrücke mit r-Wert-Referenzen (8.3.2). [Beispiel: Das Ergebnis des Aufrufs einer Funktion, deren Rückgabetyp eine rWert-Referenz ist, ist ein xWert. —End Beispiel]
  • Ein glvalue ("generalisierter" lvalue) ist ein lvalue oder ein xvalue.
  • Ein rWert (historisch gesehen so genannt, weil rWerte auf der rechten Seite eines Zuweisungsausdrucks erscheinen könnten) ist ein xWert, a
    Temporäres Objekt (12.2) oder Unterobjekt davon oder ein Wert, der nicht ist
    mit einem Objekt verbunden.
  • Ein prvalue ("reiner" rvalue) ist ein rvalue, der kein xvalue ist. [Beispiel: Das Ergebnis des Aufrufs einer Funktion, deren Rückgabetyp nicht a ist
    Referenz ist ein Wert. Der Wert eines Literal wie 12, 7.3e5 oder
    wahr ist auch ein Wert. —End Beispiel]

Jeder Ausdruck gehört zu genau einer der grundlegenden Klassifikationen in dieser Taxonomie: Ivalue, Xvalue oder Prvalue. Diese Eigenschaft eines Ausdrucks wird als Wertkategorie bezeichnet.

Ich bin mir jedoch nicht ganz sicher, ob dieser Unterabschnitt ausreicht, um die Konzepte klar zu verstehen, da "normalerweise" nicht wirklich allgemein ist, "kurz vor dem Ende seiner Lebensdauer" nicht wirklich konkret ist und "Verweise auf Werte" nicht wirklich klar ist. und "Beispiel: Das Ergebnis des Aufrufs einer Funktion, deren Rückgabetyp eine rWert-Referenz ist, ist ein xWert." klingt wie eine Schlange seinen Schwanz beißt.

PRIMARY VALUE CATEGORIES

Jeder Ausdruck gehört zu genau einer Primärwertkategorie. Diese Wertekategorien sind lvalue-, xvalue- und prvalue-Kategorien.

lWerte

Der Ausdruck E gehört genau dann zur Wertkategorie, wenn sich E auf eine Entität bezieht, die BEREITS eine Identität (Adresse, Name oder Alias) hatte, die es außerhalb von E zugänglich macht.

#include <iostream>

int i=7;

const int& f(){
    return i;
}

int main()
{
    std::cout<<&"www"<<std::endl; // The expression "www" in this row is an lvalue expression, because string literals are arrays and every array has an address.  

    i; // The expression i in this row is an lvalue expression, because it refers to the same entity ...
    i; // ... as the entity the expression i in this row refers to.

    int* p_i=new int(7);
    *p_i; // The expression *p_i in this row is an lvalue expression, because it refers to the same entity ...
    *p_i; // ... as the entity the expression *p_i in this row refers to.

    const int& r_I=7;
    r_I; // The expression r_I in this row is an lvalue expression, because it refers to the same entity ...
    r_I; // ... as the entity the expression r_I in this row refers to.

    f(); // The expression f() in this row is an lvalue expression, because it refers to the same entity ...
    i; // ... as the entity the expression f() in this row refers to.

    return 0;
}

xWerte

Der Ausdruck E gehört genau dann zur Kategorie xvalue, wenn er ist.

- das Ergebnis des impliziten oder expliziten Aufrufs einer Funktion, deren Rückgabetyp eine R-Wert-Referenz auf den Typ des zurückgegebenen Objekts ist, oder

int&& f(){
    return 3;
}

int main()
{
    f(); // The expression f() belongs to the xvalue category, because f() return type is an rvalue reference to object type.

    return 0;
}

- eine Umwandlung in einen Wert, der auf den Objekttyp verweist, oder

int main()
{
    static_cast<int&&>(7); // The expression static_cast<int&&>(7) belongs to the xvalue category, because it is a cast to an rvalue reference to object type.
    std::move(7); // std::move(7) is equivalent to static_cast<int&&>(7).

    return 0;
}

- ein Klassenmitglied-Zugriffsausdruck, der ein nicht statisches Datenmitglied eines Nichtreferenztyps bezeichnet, in dem der Objektausdruck ein x-Wert ist, oder

struct As
{
    int i;
};

As&& f(){
    return As();
}

int main()
{
    f().i; // The expression f().i belongs to the xvalue category, because As::i is a non-static data member of non-reference type, and the subexpression f() belongs to the xvlaue category.

    return 0;
}

- ein Zeiger auf einen Mitgliedsausdruck, in dem der erste Operand ein x-Wert und der zweite Operand ein Zeiger auf ein Datenmitglied ist.

Beachten Sie, dass die oben genannten Regeln bewirken, dass benannte R-Wert-Verweise auf Objekte als I-Werte und unbenannte R-Wert-Verweise auf Objekte als X-Werte behandelt werden. rWertverweise auf Funktionen werden als lWerte behandelt, unabhängig davon, ob sie benannt sind oder nicht.

#include <functional>

struct As
{
    int i;
};

As&& f(){
    return As();
}

int main()
{
    f(); // The expression f() belongs to the xvalue category, because it refers to an unnamed rvalue reference to object.
    As&& rr_a=As();
    rr_a; // The expression rr_a belongs to the lvalue category, because it refers to a named rvalue reference to object.
    std::ref(f); // The expression std::ref(f) belongs to the lvalue category, because it refers to an rvalue reference to function.

    return 0;
}

prvalues ​​

Der Ausdruck E gehört genau dann zur Kategorie prvalue, wenn E weder zur Kategorie lvalue noch zur Kategorie xvalue gehört.

struct As
{
    void f(){
        this; // The expression this is a prvalue expression. Note, that the expression this is not a variable.
    }
};

As f(){
    return As();
}

int main()
{
    f(); // The expression f() belongs to the prvalue category, because it belongs neither to the lvalue nor to the xvalue category.

    return 0;
}

MISCHWERTKATEGORIEN

Es gibt zwei weitere wichtige Mischwertkategorien. Diese Wertkategorien sind rvalue- und glvalue-Kategorien.

rWerte

Der Ausdruck E gehört genau dann zur Kategorie rvalue, wenn E zur Kategorie xvalue oder zur Kategorie prvalue gehört.

Beachten Sie, dass diese Definition bedeutet, dass der Ausdruck E genau dann zur Kategorie rvalue gehört, wenn sich E auf eine Entität bezieht, die keine Identität hat, die sie außerhalb von E YET zugänglich macht.

glvalues ​​

Der Ausdruck E gehört genau dann zur Kategorie glvalue, wenn E zur Kategorie lvalue oder zur Kategorie xvalue gehört.

EINE PRAKTISCHE REGEL

Scott Meyer hat veröffentlicht eine sehr nützliche Faustregel, um r-Werte von l-Werten zu unterscheiden.

  • Wenn Sie die Adresse eines Ausdrucks übernehmen können, ist der Ausdruck ein lWert.
  • Wenn der Typ eines Ausdrucks eine Wertreferenz ist (z. B. T & oder const T & usw.), ist dieser Ausdruck ein Wert.
  • Andernfalls ist der Ausdruck ein Wert. Konzeptionell (und in der Regel auch tatsächlich) entsprechen r-Werte temporären Objekten, z. B. solchen, die von Funktionen zurückgegeben oder durch implizite Typkonvertierungen erstellt wurden. Die meisten Literalwerte (z. B. 10 und 5,3) sind ebenfalls r-Werte.
43
Dániel Sándor

Die Kategorien in C++ 03 sind zu eingeschränkt, um die Einführung von rWert-Referenzen in Ausdrucksattribute korrekt zu erfassen.

Bei der Einführung wurde gesagt, dass eine unbenannte rvalue-Referenz einen rvalue ergibt, sodass die Überladungsauflösung rvalue-Referenzbindungen bevorzugt, wodurch Move-Konstruktoren gegenüber Copy-Konstruktoren ausgewählt werden. Es hat sich jedoch herausgestellt, dass dies Probleme verursacht, z. B. mit Dynamic Types und mit Qualifikationen.

Um dies zu zeigen, ziehen Sie in Betracht

int const&& f();

int main() {
  int &&i = f(); // disgusting!
}

Bei Entwürfen vor xvalue war dies zulässig, da in C++ 03 Werte von Nichtklassentypen niemals cv-qualifiziert sind. Es ist jedoch beabsichtigt, dass const für den Fall rvalue-reference gilt, da hier do auf Objekte (= Speicher!) Verwiesen wird und const von Nicht-Klassen gelöscht wird rvalues ​​liegt hauptsächlich daran, dass sich kein Objekt in der Nähe befindet.

Das Problem bei dynamischen Typen ist von ähnlicher Natur. In C++ 03 haben Werte vom Typ class einen bekannten dynamischen Typ - es ist der statische Typ dieses Ausdrucks. Denn um es anders zu haben, brauchen Sie Referenzen oder Dereferenzen, die zu einem Wert ausgewertet werden. Dies gilt nicht für unbenannte R-Wert-Referenzen, sie können jedoch polymorphes Verhalten zeigen. Also, um es zu lösen,

  • unbenannte rvalue-Referenzen werden zu xvalues. Sie können qualifiziert sein und möglicherweise einen anderen dynamischen Typ haben. Sie bevorzugen, wie beabsichtigt, rWert-Referenzen während des Überladens und binden sich nicht an nicht konstante lWert-Referenzen.

  • Was zuvor ein rWert war (Literale, Objekte, die durch Umwandlungen in Nichtreferenztypen erstellt wurden), wird jetzt zu prWert. Sie haben die gleiche Präferenz wie x-Werte beim Überladen.

  • Was früher ein lWert war, bleibt ein lWert.

Und zwei Gruppierungen werden durchgeführt, um diejenigen zu erfassen, die qualifiziert werden können und unterschiedliche dynamische Typen haben können (glvalues), und diejenigen, bei denen Überladung eine rWertreferenzbindung bevorzugt (rvalues).

Ich habe lange damit gekämpft, bis ich auf die Erklärung von cppreference.com für Wertkategorien stieß.

Eigentlich ist es ziemlich einfach, aber ich finde, dass es oft so erklärt wird, dass man es sich nur schwer merken kann. Hier wird es sehr schematisch erklärt. Ich zitiere einige Teile der Seite:

Primäre Kategorien

Die Primärwertkategorien entsprechen zwei Eigenschaften von Ausdrücken:

  • hat Identität : Es ist möglich zu bestimmen, ob sich der Ausdruck auf dieselbe Entität wie ein anderer Ausdruck bezieht, z. B. durch Vergleichen der Adressen der Objekte oder der Funktionen, die sie identifizieren (direkt oder indirekt erhalten). ;

  • kann von verschoben werden: move -Konstruktor, move-Zuweisungsoperator oder eine andere Funktionsüberladung, die die Verschiebungssemantik implementiert, kann an den Ausdruck gebunden werden.

Ausdrücke, die:

  • haben Identität und können nicht verschoben werden heißen lWertausdrücke ;
  • haben Identität und können verschoben werden von werden aufgerufen xvalue Ausdrücke ;
  • habe keine Identität und kann verschoben werden von werden aufgerufen prvalue expressions ;
  • keine Identität haben und nicht verschoben werden können, werden nicht verwendet.

wert

Ein lvalue-Ausdruck ("left value") ist ein Ausdruck, bei dem die Identität hat und nicht von verschoben werden kann.

rvalue (bis C++ 11), prvalue (seit C++ 11)

Ein prvalue-Ausdruck ("pure rvalue") ist ein Ausdruck, bei dem keine Identität hat und von verschoben werden kann.

xvalue

Ein xvalue-Ausdruck ("Expiring Value") ist ein Ausdruck, bei dem die Identität hat und von verschoben werden kann.

glvalue

Ein glvalue-Ausdruck ("generalized lvalue") ist ein Ausdruck, der entweder lvalue oder xvalue ist. Es hat Identität . Es kann verschoben werden oder nicht.

rvalue (seit C++ 11)

Ein rvalue-Ausdruck ("rechter Wert") ist ein Ausdruck, der entweder ein prvalue-Ausdruck oder ein xvalue-Ausdruck ist. Es kann von verschoben werden. Es kann Identität haben oder nicht.

25
Felix Dombek

In welcher Beziehung stehen diese neuen Kategorien zu den vorhandenen Kategorien rvalue und lvalue?

Ein C++ 03-Wert ist immer noch ein C++ 11-Wert, während ein C++ 03-Wert in C++ 11 als prvalue bezeichnet wird.

16
fredoverflow

Ein Nachtrag zu den hervorragenden Antworten oben in einem Punkt, der mich verwirrte, selbst nachdem ich Stroustrup gelesen und geglaubt hatte, den Unterschied zwischen Wert und Wert zu verstehen. Wenn du siehst

int&& a = 3,

es ist sehr verlockend, den int&& als Typ zu lesen und daraus zu schließen, dass a ein Wert ist. Es ist nicht:

int&& a = 3;
int&& c = a; //error: cannot bind 'int' lvalue to 'int&&'
int& b = a; //compiles

a hat einen Namen und ist ipso facto ein Wert. Denken Sie nicht an den && als Teil der Art von a; Es ist nur etwas, das Ihnen sagt, an was a gebunden werden darf.

Dies gilt insbesondere für Argumente vom Typ T&& in Konstruktoren. Wenn du schreibst

Foo::Foo(T&& _t) : t{_t} {}

sie werden _t in t kopieren. Du brauchst

Foo::Foo(T&& _t) : t{std::move(_t)} {}, wenn Sie sich bewegen möchten. Würde mich mein Compiler warnen, wenn ich das move weglasse?

14
Mohan

Da in den vorherigen Antworten die Theorie hinter den Wertkategorien ausführlich behandelt wurde, möchte ich noch etwas hinzufügen: Sie können tatsächlich damit spielen und es testen.

Für einige praktische Experimente mit den Wertekategorien können Sie den Decltype-Bezeichner verwenden. Sein Verhalten unterscheidet explizit zwischen den drei primären Wertkategorien (xvalue, lvalue und prvalue).

Die Verwendung des Präprozessors erspart uns die Eingabe ...

Hauptkategorien:

#define IS_XVALUE(X) std::is_rvalue_reference<decltype((X))>::value
#define IS_LVALUE(X) std::is_lvalue_reference<decltype((X))>::value
#define IS_PRVALUE(X) !std::is_reference<decltype((X))>::value

Gemischte Kategorien:

#define IS_GLVALUE(X) IS_LVALUE(X) || IS_XVALUE(X)
#define IS_RVALUE(X) IS_PRVALUE(X) || IS_XVALUE(X)

Jetzt können wir (fast) alle Beispiele aus cppreference on value category reproduzieren.

Hier einige Beispiele mit C++ 17 (für terse static_assert):

void doesNothing(){}
struct S
{
    int x{0};
};
int x = 1;
int y = 2;
S s;

static_assert(IS_LVALUE(x));
static_assert(IS_LVALUE(x+=y));
static_assert(IS_LVALUE("Hello world!"));
static_assert(IS_LVALUE(++x));

static_assert(IS_PRVALUE(1));
static_assert(IS_PRVALUE(x++));
static_assert(IS_PRVALUE(static_cast<double>(x)));
static_assert(IS_PRVALUE(std::string{}));
static_assert(IS_PRVALUE(throw std::exception()));
static_assert(IS_PRVALUE(doesNothing()));

static_assert(IS_XVALUE(std::move(s)));
// The next one doesn't work in gcc 8.2 but in gcc(trunk). Clang 7.0.0 and msvc 19.16 are doing fine.
static_assert(IS_XVALUE(S().x)); 

Die gemischten Kategorien sind etwas langweilig, sobald Sie die Hauptkategorie herausgefunden haben.

Weitere Beispiele (und Experimente) finden Sie unter Link im Compiler-Explorer . Aber lesen Sie nicht die Versammlung. Ich habe viele Compiler hinzugefügt, um sicherzustellen, dass es auf allen gängigen Compilern funktioniert.

3
thebrandre