Namespaces
Variants

Value categories

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Value categories
Order of evaluation
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

Jeder C++ Ausdruck (ein Operator mit seinen Operanden, ein Literal, ein Variablenname, etc.) ist durch zwei unabhängige Eigenschaften charakterisiert: einen Typ und eine Wertkategorie . Jeder Ausdruck hat einen bestimmten Nicht-Referenz-Typ, und jeder Ausdruck gehört zu genau einer der drei primären Wertkategorien: prvalue , xvalue , und lvalue .

  • ein glvalue („generalisierter“ lvalue) ist ein Ausdruck, dessen Auswertung die Identität eines Objekts oder einer Funktion bestimmt;
  • ein prvalue („reiner“ rvalue) ist ein Ausdruck, dessen Auswertung
  • berechnet den Wert eines Operanden eines eingebauten Operators (ein solcher Prvalue hat kein Ergebnisobjekt ), oder
  • initialisiert ein Objekt (ein solcher Prvalue wird als mit einem Ergebnisobjekt bezeichnet).
Das Ergebnisobjekt kann eine Variable, ein durch new-expression erzeugtes Objekt, ein durch temporary materialization erzeugtes Temporärobjekt oder ein Mitglied davon sein. Beachten Sie, dass nicht- void discarded Ausdrücke ein Ergebnisobjekt haben (das materialisierte Temporärobjekt). Außerdem hat jeder Klassen- und Array-Prvalue ein Ergebnisobjekt, außer wenn er der Operand von decltype ist;
  • ein xvalue (ein "eXpiring"-Wert) ist ein Glvalue, das ein Objekt bezeichnet, dessen Ressourcen wiederverwendet werden können;
  • ein lvalue ist ein Glvalue, das kein xvalue ist;
Erweiterter Inhalt

Historisch so genannt, weil lvalues auf der linken Seite eines Zuweisungsausdrucks erscheinen konnten. Im Allgemeinen trifft dies nicht immer zu:

void foo();
void baz()
{
    int a; // Expression `a` is lvalue
    a = 4; // OK, could appear on the left-hand side of an assignment expression
    int &b{a}; // Expression `b` is lvalue
    b = 5; // OK, could appear on the left-hand side of an assignment expression
    const int &c{a}; // Expression `c` is lvalue
    c = 6;           // ill-formed, assignment of read-only reference
    // Expression `foo` is lvalue
    // address may be taken by built-in address-of operator
    void (*p)() = &foo;
    foo = baz; // ill-formed, assignment of function
}
  • ein rvalue ist ein prvalue oder ein xvalue;
Erweiterter Inhalt

Historisch so genannt, weil rvalues auf der rechten Seite einer Zuweisung erscheinen konnten. Im Allgemeinen trifft dies jedoch nicht immer zu:

#include <iostream>
struct S
{
    S() : m{42} {}
    S(int a) : m{a} {}
    int m;
};
int main()
{
    S s;
    // Expression `S{}` is prvalue
    // May appear on the right-hand side of an assignment expression
    s = S{};
    std::cout << s.m << '\n';
    // Expression `S{}` is prvalue
    // Can be used on the left-hand side too
    std::cout << (S{} = S{7}).m << '\n';
}

Ausgabe:

42
7

Hinweis: Diese Taxonomie durchlief erhebliche Änderungen mit früheren C++-Standardrevisionen, siehe Verlauf unten für Details.

Erweiterter Inhalt

Trotz ihrer Namen klassifizieren diese Begriffe Ausdrücke, nicht Werte.

#include <type_traits>
#include <utility>
template <class T> struct is_prvalue : std::true_type {};
template <class T> struct is_prvalue<T&> : std::false_type {};
template <class T> struct is_prvalue<T&&> : std::false_type {};
template <class T> struct is_lvalue : std::false_type {};
template <class T> struct is_lvalue<T&> : std::true_type {};
template <class T> struct is_lvalue<T&&> : std::false_type {};
template <class T> struct is_xvalue : std::false_type {};
template <class T> struct is_xvalue<T&> : std::false_type {};
template <class T> struct is_xvalue<T&&> : std::true_type {};
int main()
{
    int a{42};
    int& b{a};
    int&& r{std::move(a)};
    // Expression `42` is prvalue
    static_assert(is_prvalue<decltype((42))>::value);
    // Expression `a` is lvalue
    static_assert(is_lvalue<decltype((a))>::value);
    // Expression `b` is lvalue
    static_assert(is_lvalue<decltype((b))>::value);
    // Expression `std::move(a)` is xvalue
    static_assert(is_xvalue<decltype((std::move(a)))>::value);
    // Type of variable `r` is rvalue reference
    static_assert(std::is_rvalue_reference<decltype(r)>::value);
    // Type of variable `b` is lvalue reference
    static_assert(std::is_lvalue_reference<decltype(b)>::value);
    // Expression `r` is lvalue
    static_assert(is_lvalue<decltype((r))>::value);
}

Inhaltsverzeichnis

Hauptkategorien

lvalue

Die folgenden Ausdrücke sind lvalue expressions :

Erweiterter Inhalt
void foo() {}
void baz()
{
    // `foo` ist ein Lvalue
    // Adresse kann durch eingebauten Adressoperator genommen werden
    void (*p)() = &foo;
}
struct foo {};
template <foo a>
void baz()
{
    const foo* obj = &a;  // `a` ist ein Lvalue, Template-Parameter-Objekt
}
  • Ein Funktionsaufruf oder ein überladener Operatorausdruck, dessen Rückgabetyp ein Lvalue-Referenz ist, wie z.B. std:: getline ( std:: cin , str ) , std:: cout << 1 , str1 = str2 , oder ++ it ;
Erweiterter Inhalt
int& a_ref()
{
    static int a{3};
    return a;
}
void foo()
{
    a_ref() = 5;  // `a_ref()` ist ein Lvalue, Funktionsaufruf dessen Rückgabetyp eine Lvalue-Referenz ist
}
Erweiterter Inhalt
struct foo
{
    enum bar
    {
        m // member enumerator
    };
};
void baz()
{
    foo a;
    a.m = 42; // ill-formed, lvalue required as left operand of assignment
}
struct foo
{
    void m() {} // non-static member function
};
void baz()
{
    foo a;
    // `a.m` is a prvalue, hence the address cannot be taken by built-in
    // address-of operator
    void (foo::*p1)() = &a.m; // ill-formed
    void (foo::*p2)() = &foo::m; // OK: pointer to member function
}
struct foo
{
    static void m() {} // static member function
};
void baz()
{
    foo a;
    void (*p1)() = &a.m;     // `a.m` is an lvalue
    void (*p2)() = &foo::m;  // the same
}
template <int& v>
void set()
{
    v = 5; // Template-Parameter ist Lvalue
}
int a{3}; // Statische Variable, feste Adresse ist zur Compilezeit bekannt
void foo()
{
    set<a>();
}
  • ein Funktionsaufruf oder ein überladener Operatorausdruck, dessen Rückgabetyp ein Rvalue-Referenz auf Funktion ist;
  • ein Cast-Ausdruck zu Rvalue-Referenz auf Funktionstyp, wie z.B. static_cast < void ( && ) ( int ) > ( x ) .
(seit C++11)

Eigenschaften:

  • Gleich wie glvalue (unten).
  • Die Adresse eines lvalue kann mit dem eingebauten Adressoperator genommen werden: & ++ i [1] und & std:: hex sind gültige Ausdrücke.
  • Ein modifizierbares lvalue kann als linker Operand der eingebauten Zuweisungs- und zusammengesetzten Zuweisungsoperatoren verwendet werden.
  • Ein lvalue kann verwendet werden, um eine lvalue-Referenz zu initialisieren ; dies assoziiert einen neuen Namen mit dem durch den Ausdruck identifizierten Objekt.

prvalue

Die folgenden Ausdrücke sind prvalue-Ausdrücke :

template <int v>
void foo()
{
    // kein Lvalue, `v` ist ein Template-Parameter vom skalaren Typ int
    const int* a = &v; // ungültig
    v = 3; // ungültig: Lvalue erforderlich als linker Operand der Zuweisung
}
(seit C++11)
(seit C++20)

Eigenschaften:

  • Gleich wie rvalue (unten).
  • Ein prvalue kann nicht polymorph sein: Der dynamische Typ des bezeichneten Objekts entspricht immer dem Typ des Ausdrucks.
  • Ein prvalue ohne Klassen- oder Array-Typ kann nicht cv-qualifiziert sein , es sei denn, es wird materialisiert , um an eine Referenz auf einen cv-qualifizierten Typ gebunden zu werden (seit C++17) . (Hinweis: Ein Funktionsaufruf oder eine Typumwandlung kann einen prvalue eines nicht-klassen cv-qualifizierten Typs ergeben, aber die CV-Qualifizierung wird in der Regel sofort entfernt.)
  • Ein prvalue kann keinen unvollständigen Typ haben (außer vom Typ void , siehe unten, oder bei Verwendung in einem decltype -Spezifizierer).
  • Ein prvalue kann keinen abstrakten Klassentyp oder ein Array davon haben.

xvalue

Die folgenden Ausdrücke sind xvalue-Ausdrücke :

  • einen Funktionsaufruf oder einen überladenen Operatorausdruck, dessen Rückgabetyp ein Rvalue-Referenz auf ein Objekt ist, wie z.B. std :: move ( x ) ;
  • a [ n ] , den eingebauten Subskript-Operator , bei dem ein Operand ein Array-Rvalue ist;
  • einen Cast-Ausdruck in einen Rvalue-Referenz-Objekttyp, wie z.B. static_cast < char && > ( x ) ;
(seit C++11)
(seit C++17)
(seit C++23)

Eigenschaften:

  • Gleich wie Rvalue (unten).
  • Gleich wie Glvalue (unten).

Insbesondere binden, wie alle R-Werte, X-Werte an R-Wert-Referenzen, und wie alle GL-Werte können X-Werte polymorph sein, und nicht-Klasse-X-Werte können CV-qualifiziert sein.

Erweiterter Inhalt
#include <type_traits>
template <class T> struct is_prvalue : std::true_type {};
template <class T> struct is_prvalue<T&> : std::false_type {};
template <class T> struct is_prvalue<T&&> : std::false_type {};
template <class T> struct is_lvalue : std::false_type {};
template <class T> struct is_lvalue<T&> : std::true_type {};
template <class T> struct is_lvalue<T&&> : std::false_type {};
template <class T> struct is_xvalue : std::false_type {};
template <class T> struct is_xvalue<T&> : std::false_type {};
template <class T> struct is_xvalue<T&&> : std::true_type {};
// Beispiel aus C++23 Standard: 7.2.1 Wertkategorie [basic.lval]
struct A
{
    int m;
};
A&& operator+(A, A);
A&& f();
int main()
{
    A a;
    A&& ar = static_cast<A&&>(a);
    // Funktionsaufruf mit Rückgabetyp Rvalue-Referenz ist Xvalue
    static_assert(is_xvalue<decltype( (f()) )>::value);
    // Member eines Objektausdrucks, Objekt ist Xvalue, `m` ist ein nicht-statisches Datenelement
    static_assert(is_xvalue<decltype( (f().m) )>::value);
    // Ein Cast-Ausdruck zu Rvalue-Referenz
    static_assert(is_xvalue<decltype( (static_cast<A&&>(a)) )>::value);
    // Operatorausdruck, dessen Rückgabetyp Rvalue-Referenz auf Objekt ist
    static_assert(is_xvalue<decltype( (a + a) )>::value);
    // Ausdruck `ar` ist Lvalue, `&ar` ist gültig
    static_assert(is_lvalue<decltype( (ar) )>::value);
    [[maybe_unused]] A* ap = &ar;
}

Gemischte Kategorien

glvalue

Ein glvalue-Ausdruck ist entweder ein lvalue oder ein xvalue.

Eigenschaften:

  • Ein glvalue kann implizit in einen prvalue mittels Lvalue-zu-Rvalue-, Array-zu-Zeiger- oder Funktion-zu-Zeiger- impliziter Konvertierung umgewandelt werden.
  • Ein glvalue kann polymorph sein: Der dynamische Typ des Objekts, das es identifiziert, entspricht nicht notwendigerweise dem statischen Typ des Ausdrucks.
  • Ein glvalue kann, sofern durch den Ausdruck erlaubt, einen unvollständigen Typ haben.

rvalue

Ein rvalue-Ausdruck ist entweder ein prvalue oder ein xvalue.

Eigenschaften:

  • Die Adresse eines R-Werts kann nicht mit dem eingebauten Adressoperator genommen werden: & int ( ) , & i ++ [3] , & 42 und & std :: move ( x ) sind ungültig.
  • Ein R-Wert kann nicht als linker Operand der eingebauten Zuweisungs- oder zusammengesetzten Zuweisungsoperatoren verwendet werden.
  • Ein R-Wert kann verwendet werden, um eine konstante L-Wert-Referenz zu initialisieren , wobei die Lebensdauer des durch den R-Wert identifizierten temporären Objekts verlängert wird , bis der Gültigkeitsbereich der Referenz endet.
  • Ein Rvalue kann verwendet werden, um eine Rvalue-Referenz zu initialisieren , wobei die Lebensdauer des durch den Rvalue identifizierten temporären Objekts verlängert wird, bis der Gültigkeitsbereich der Referenz endet.
  • Wenn es als Funktionsargument verwendet wird und zwei Überladungen der Funktion verfügbar sind, von denen eine einen Rvalue-Referenzparameter und die andere einen Lvalue-Referenzparameter auf const nimmt, bindet ein Rvalue an die Rvalue-Referenz-Überladung (daher ruft ein Rvalue-Argument den Move-Konstruktor auf, wenn sowohl Kopier- als auch Move-Konstruktoren verfügbar sind, und ebenso mit Kopier- und Move-Zuweisungsoperatoren).
(seit C++11)

Spezielle Kategorien

Ausstehender Member-Funktionsaufruf

Die Ausdrücke a. mf und p - > mf , wobei mf eine nicht-statische Memberfunktion ist, sowie die Ausdrücke a. * pmf und p - > * pmf , wobei pmf ein Zeiger auf Memberfunktion ist, werden als Prvalue-Ausdrücke klassifiziert. Sie können jedoch nicht zur Initialisierung von Referenzen, als Funktionsargumente oder für jegliche anderen Zwecke verwendet werden, außer als linkes Argument des Funktionsaufrufoperators, z.B. ( p - > * pmf ) ( args ) .

Void-Ausdrücke

Funktionsaufrufausdrücke, die void zurückgeben, Cast-Ausdrücke zu void und Throw-Ausdrücke werden als Prvalue-Ausdrücke klassifiziert, können jedoch nicht zur Initialisierung von Referenzen oder als Funktionsargumente verwendet werden. Sie können in verworfenen Wertkontexten verwendet werden (z.B. in einer eigenen Zeile, als linke Operanden des Kommaoperators usw.) und in der return -Anweisung einer Funktion, die void zurückgibt. Zusätzlich können Throw-Ausdrücke als zweite und dritte Operanden des bedingten Operators ?: verwendet werden.

Void-Ausdrücke haben kein Ergebnisobjekt .

(since C++17)

Bitfelder

Ein Ausdruck, der ein Bit-Feld bezeichnet (z.B. a. m , wobei a ein L-Wert vom Typ struct A { int m : 3 ; } ist) ist ein Glvalue-Ausdruck: Er kann als linker Operand des Zuweisungsoperators verwendet werden, aber seine Adresse kann nicht genommen werden und eine nicht-konstante Lvalue-Referenz kann nicht daran gebunden werden. Eine konstante Lvalue-Referenz oder Rvalue-Referenz kann von einem Bit-Feld-Glvalue initialisiert werden, jedoch wird eine temporäre Kopie des Bit-Felds erstellt: Sie bindet nicht direkt an das Bit-Feld.

Move-eligible Ausdrücke

Obwohl ein Ausdruck, der aus dem Namen einer beliebigen Variable besteht, ein Lvalue-Ausdruck ist, kann ein solcher Ausdruck move-eligible sein, wenn er als Operand erscheint von

Wenn ein Ausdruck move-eligible ist, wird er für den Zweck der Überladungsauflösung behandelt entweder als Rvalue oder als Lvalue (bis C++23) als Rvalue (seit C++23) (daher kann er den Move-Konstruktor auswählen). Siehe Automatisches Verschieben von lokalen Variablen und Parametern für Details.

(seit C++11)

Geschichte

CPL

Die Programmiersprache CPL führte als erste Wertkategorien für Ausdrücke ein: Alle CPL-Ausdrücke können im "rechten Modus" ausgewertet werden, aber nur bestimmte Arten von Ausdrücken sind im "linken Modus" sinnvoll. Bei der Auswertung im rechten Modus wird ein Ausdruck als Regel zur Berechnung eines Werts betrachtet (der rechte Wert, oder rvalue ). Bei der Auswertung im linken Modus liefert ein Ausdruck effektiv eine Adresse (der linke Wert, oder lvalue ). "Links" und "Rechts" standen hier für "links der Zuweisung" und "rechts der Zuweisung".

C

Die Programmiersprache C folgte einer ähnlichen Taxonomie, mit der Ausnahme, dass die Rolle der Zuweisung nicht mehr signifikant war: C-Ausdrücke werden zwischen "Lvalue-Ausdrücken" und anderen (Funktionen und Nicht-Objekt-Werten) kategorisiert, wobei "Lvalue" einen Ausdruck bedeutet, der ein Objekt identifiziert, einen "Locator Value" [4] .

C++98

Pre-2011 C++ folgte dem C-Modell, stellte jedoch den Namen "Rvalue" für Nicht-Lvalue-Ausdrücke wieder her, machte Funktionen zu Lvalues und fügte die Regel hinzu, dass Referenzen an Lvalues gebunden werden können, aber nur Referenzen auf const an Rvalues gebunden werden können. Mehrere Nicht-Lvalue-C-Ausdrücke wurden in C++ zu Lvalue-Ausdrücken.

C++11

Mit der Einführung von Move-Semantik in C++11 wurden Wertkategorien neu definiert, um zwei unabhängige Eigenschaften von Ausdrücken zu charakterisieren [5] :

  • hat Identität : es ist möglich zu bestimmen, ob der Ausdruck auf dieselbe Entität verweist wie ein anderer Ausdruck, beispielsweise durch Vergleich der Adressen der Objekte oder der Funktionen, auf die sie verweisen (direkt oder indirekt erhalten);
  • kann verschoben werden : Move-Konstruktor , Move-Zuweisungsoperator , oder eine andere Funktionsüberladung, die Move-Semantik implementiert, kann an den Ausdruck gebunden werden.

In C++11, Ausdrücke, die:

  • Identität besitzen und nicht verschoben werden können, werden lvalue -Ausdrücke genannt;
  • Identität besitzen und verschoben werden können, werden xvalue -Ausdrücke genannt;
  • keine Identität besitzen und verschoben werden können, werden prvalue ("pure rvalue")-Ausdrücke genannt;
  • keine Identität besitzen und nicht verschoben werden können, werden nicht verwendet [6] .

Ausdrücke, die Identität besitzen, werden "glvalue-Ausdrücke" genannt (glvalue steht für "generalized lvalue"). Sowohl Lvalues als auch Xvalues sind glvalue-Ausdrücke.

Die Ausdrücke, von denen verschoben werden kann, werden "Rvalue-Ausdrücke" genannt. Sowohl Prvalues als auch Xvalues sind Rvalue-Ausdrücke.

C++17

In C++17 wurde Copy Elision in einigen Situationen verbindlich, was die Trennung von Prvalue-Ausdrücken von den durch sie initialisierten temporären Objekten erforderte, was zu dem heute vorhandenen System führte. Beachten Sie, dass im Gegensatz zum C++11-Schema Prvalues nicht mehr verschoben werden.

Fußnoten

  1. Unter der Annahme, dass i einen eingebauten Typ hat oder der Prä-Inkrement-Operator überladen ist, um eine Lvalue-Referenz zurückzugeben.
  2. 2.0 2.1 2.2 2.3 Spezielle Rvalue-Kategorie, siehe ausstehender Member-Funktionsaufruf .
  3. Unter der Annahme, dass i einen eingebauten Typ hat oder der Post-Inkrement-Operator nicht überladen ist, um eine Lvalue-Referenz zurückzugeben.
  4. "Eine Meinungsverschiedenheit innerhalb der C-Community drehte sich um die Bedeutung von Lvalue: Eine Gruppe betrachtete ein Lvalue als jede Art von Objektlokator, während eine andere Gruppe der Ansicht war, dass ein Lvalue auf der linken Seite eines Zuweisungsoperators bedeutungsvoll sei. Das C89-Komitee übernahm die Definition von Lvalue als Objektlokator." -- ANSI C Rationale, 6.3.2.1/10.
  5. "Neue" Wert-Terminologie von Bjarne Stroustrup, 2010.
  6. const-Prvalues (nur für Klassentypen erlaubt) und const-Xvalues binden nicht an T&& Überladungen, aber sie binden an die const T && Überladungen, die ebenfalls vom Standard als "Move-Konstruktor" und "Move-Zuweisungsoperator" klassifiziert werden und damit die Definition von "kann verschoben werden" für diese Klassifikation erfüllen. Allerdings können solche Überladungen ihre Argumente nicht modifizieren und werden in der Praxis nicht verwendet; in deren Abwesenheit binden const-Prvalues und const-Xvalues an const T & Überladungen.

Referenzen

  • C++23-Standard (ISO/IEC 14882:2024):
  • 7.2.1 Wertkategorie [basic.lval]
  • C++20-Standard (ISO/IEC 14882:2020):
  • 7.2.1 Wertkategorie [basic.lval]
  • C++17-Standard (ISO/IEC 14882:2017):
  • 6.10 Lvalues und Rvalues [basic.lval]
  • C++14-Standard (ISO/IEC 14882:2014):
  • 3.10 Lvalues und Rvalues [basic.lval]
  • C++11-Standard (ISO/IEC 14882:2011):
  • 3.10 Lvalues und Rvalues [basic.lval]
  • C++98-Standard (ISO/IEC 14882:1998):
  • 3.10 Lvalues und Rvalues [basic.lval]

Fehlerberichte

Die folgenden verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.

DR Angewendet auf Verhalten wie veröffentlicht Korrigiertes Verhalten
CWG 616 C++11 Member-Zugriff und Member-Zugriff durch
Zeiger auf Member eines Rvalue ergab Prvalue
neu klassifiziert als Xvalue
CWG 1059 C++11 Array-Prvalues konnten nicht cv-qualifiziert werden erlaubt
CWG 1213 C++11 Indexierung eines Array-Rvalue ergab Lvalue neu klassifiziert als Xvalue

Siehe auch

C-Dokumentation für Wertkategorien

Externe Links

1. C++ Wertkategorien und decltype entschlüsselt — David Mazières, 2021
2. Wertkategorie eines Ausdrucks empirisch bestimmen — StackOverflow