Namensräume
Varianten
Aktionen

Wertkategorien

Von cppreference.com
< cpp‎ | Sprache
 
 
C++ Sprache
Allgemeine Themen
Kontrollfluss
Bedingte Ausführungsaussagen
if
Iterationsanweisungen (Schleifen)
for
Bereichs-for (C++11)
Sprunganweisungen
Funktionen
Funktionsdeklaration
Lambda-Funktionsausdruck
inline-Spezifizierer
Dynamische Ausnahmespezifikationen (bis C++17*)
noexcept-Spezifizierer (C++11)
Ausnahmen
Namensräume
Typen
Spezifizierer
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Speicherdauer-Spezifizierer
Initialisierung
Ausdrücke
Alternative Darstellungen
Literale
Boolesch - Ganzzahl - Gleitkommazahl
Zeichen - String - nullptr (C++11)
Benutzerdefinierte (C++11)
Dienstprogramme
Attribute (C++11)
Typen
typedef-Deklaration
Typalias-Deklaration (C++11)
Umwandlungen
Speicherzuweisung
Klassen
Klassenspezifische Funktionseigenschaften
explicit (C++11)
static

Spezielle Member-Funktionen
Templates
Sonstiges
 
 

Jeder C++ Ausdruck (ein Operator mit seinen Operanden, ein Literal, ein Variablenname usw.) wird durch zwei unabhängige Eigenschaften charakterisiert: einen Typ und eine Wertkategorie. Jeder Ausdruck hat einen Nicht-Referenztyp und 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
  • den Wert eines Operanden eines eingebauten Operators berechnet (ein solcher prvalue hat kein Ergebnisobjekt), oder
  • ein Objekt initialisiert (ein solcher prvalue hat ein Ergebnisobjekt).
Das Ergebnisobjekt kann eine Variable sein, ein von einem new-Ausdruck erzeugtes Objekt, ein durch temporäre Materialisierung erzeugtes temporäres Objekt oder ein Teil davon. Beachten Sie, dass nicht-void verworfene Ausdrücke ein Ergebnisobjekt haben (das materialisierte temporäre Objekt). Außerdem hat jeder prvalue vom Typ Klasse oder Array ein Ergebnisobjekt, außer wenn er Operand von decltype ist;
  • ein xvalue (ein "ablaufender" Wert) ist ein glvalue, das ein Objekt bezeichnet, dessen Ressourcen wiederverwendet werden können;
  • ein lvalue ist ein glvalue, der kein xvalue ist;
Erweiterter Inhalt

Historisch so genannt, weil lvalues auf der linken Seite eines Zuweisungsausdrucks stehen konnten. Im Allgemeinen ist das nicht immer der Fall

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 eines Zuweisungsausdrucks stehen konnten. Im Allgemeinen ist das nicht immer der Fall

#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 hat sich mit früheren C++-Standardrevisionen erheblich geändert, 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);
}

Inhalt

[editieren] Primäre Kategorien

[editieren] lvalue

Die folgenden Ausdrücke sind lvalue-Ausdrücke

Erweiterter Inhalt
void foo() {}
 
void baz()
{
    // `foo` is lvalue
    // address may be taken by built-in address-of operator
    void (*p)() = &foo;
}
struct foo {};
 
template <foo a>
void baz()
{
    const foo* obj = &a;  // `a` is an lvalue, template parameter object
}
  • ein Funktionsaufruf oder ein überladener Operator-Ausdruck, dessen Rückgabetyp eine 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()` is lvalue, function call whose return type is lvalue reference
}
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 is lvalue
}
 
int a{3}; // static variable, fixed address is known at compile-time
 
void foo()
{
    set<a>();
}
  • ein Funktionsaufruf oder ein überladener Operator-Ausdruck, dessen Rückgabetyp eine rvalue-Referenz auf eine Funktion ist;
  • ein Cast-Ausdruck zu einem rvalue-Referenztyp für Funktionen, wie z. B. static_cast<void(&&)(int)>(x).
(seit C++11)

Eigenschaften

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

[editieren] prvalue

Die folgenden Ausdrücke sind prvalue-Ausdrücke

template <int v>
void foo()
{
    // not an lvalue, `v` is a template parameter of scalar type int
    const int* a = &v; // ill-formed
 
    v = 3; // ill-formed: lvalue required as left operand of assignment
}
(seit C++11)
(seit C++20)

Eigenschaften

  • Identisch mit rvalue (unten).
  • Ein prvalue kann nicht polymorph sein: der dynamische Typ des Objekts, das er bezeichnet, ist immer der Typ des Ausdrucks.
  • Ein prvalue, der kein Klassentyp und kein Array-Typ ist, kann nicht cv-qualifiziert sein, es sei denn, er wird materialisiert, um an eine Referenz auf einen cv-qualifizierten Typ gebunden zu werden(seit C++17). (Hinweis: Ein Funktionsaufruf oder ein Cast-Ausdruck kann zu einem prvalue vom nicht-Klassen-cv-qualifizierten Typ führen, aber der cv-Qualifizierer wird im Allgemeinen sofort entfernt.)
  • Ein prvalue kann keinen unvollständigen Typ haben (außer für den Typ void, siehe unten, oder wenn er in einem decltype-Spezifizierer verwendet wird).
  • Ein prvalue kann keinen abstrakten Klassentyp oder ein Array davon haben.

[editieren] xvalue

Die folgenden Ausdrücke sind xvalue-Ausdrücke

  • ein Funktionsaufruf oder ein überladener Operator-Ausdruck, dessen Rückgabetyp eine rvalue-Referenz auf ein Objekt ist, wie z. B. std::move(x);
  • a[n], der eingebaute Subscript-Operator-Ausdruck, wobei ein Operand ein Array-rvalue ist;
  • ein Cast-Ausdruck zu einem rvalue-Referenztyp für Objekte, wie z. B. static_cast<char&&>(x);
(seit C++11)
(seit C++17)
(seit C++23)

Eigenschaften

  • Identisch mit rvalue (unten).
  • Identisch mit glvalue (unten).

Insbesondere können xvalues wie alle rvalues an rvalue-Referenzen gebunden werden, und wie alle glvalues können xvalues polymorph sein, und nicht-Klassen-xvalues 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 {};
 
// Example from C++23 standard: 7.2.1 Value category [basic.lval]
struct A
{
    int m;
};
 
A&& operator+(A, A);
A&& f();
 
int main()
{
    A a;
    A&& ar = static_cast<A&&>(a);
 
    // Function call with return type rvalue reference is xvalue
    static_assert(is_xvalue<decltype( (f()) )>::value);
 
    // Member of object expression, object is xvalue, `m` is a non-static data member
    static_assert(is_xvalue<decltype( (f().m) )>::value);
 
    // A cast expression to rvalue reference
    static_assert(is_xvalue<decltype( (static_cast<A&&>(a)) )>::value);
 
    // Operator expression, whose return type is rvalue reference to object
    static_assert(is_xvalue<decltype( (a + a) )>::value);
 
    // Expression `ar` is lvalue, `&ar` is valid
    static_assert(is_lvalue<decltype( (ar) )>::value);
    [[maybe_unused]] A* ap = &ar;
}

[editieren] Gemischte Kategorien

[editieren] glvalue

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

Eigenschaften

  • Ein glvalue kann implizit in einen prvalue umgewandelt werden durch lvalue-zu-rvalue-, Array-zu-Zeiger- oder Funktions-zu-Zeiger- Implizite Konvertierung.
  • Ein glvalue kann polymorph sein: der dynamische Typ des Objekts, das er identifiziert, ist nicht notwendigerweise der statische Typ des Ausdrucks.
  • Ein glvalue kann einen unvollständigen Typ haben, wo dies durch den Ausdruck zulässig ist.

[editieren] rvalue

Ein rvalue-Ausdruck ist entweder prvalue oder xvalue.

Eigenschaften

  • Die Adresse eines rvalues kann nicht mit dem eingebauten Adressoperator genommen werden: &int(), &i++[3], &42 und &std::move(x) sind ungültig.
  • Ein rvalue kann nicht als linke Operand der eingebauten Zuweisungs- oder zusammengesetzten Zuweisungsoperatoren verwendet werden.
  • Ein rvalue kann verwendet werden, um eine const lvalue-Referenz zu initialisieren, in diesem Fall wird die Lebensdauer des durch den rvalue identifizierten temporären Objekts bis zum Ende des Gültigkeitsbereichs der Referenz erweitert.
  • Ein rvalue kann verwendet werden, um eine rvalue-Referenz zu initialisieren, in diesem Fall wird die Lebensdauer des durch den rvalue identifizierten temporären Objekts bis zum Ende des Gültigkeitsbereichs der Referenz erweitert.
  • Wenn er als Funktionsargument verwendet wird und zwei Überladungen der Funktion verfügbar sind, eine mit einem rvalue-Referenzparameter und die andere mit einem lvalue-Referenzparameter zu const, bindet ein rvalue an die rvalue-Referenzüberladung (somit wählt ein rvalue-Argument, wenn sowohl Kopier- als auch Move-Konstruktoren verfügbar sind, den Move-Konstruktor aus, und ebenso mit Kopier- und Move-Zuweisungsoperatoren).
(seit C++11)

[editieren] Spezielle Kategorien

[editieren] Ausstehender Aufruf einer Member-Funktion

Die Ausdrücke a.mf und p->mf, wobei mf eine nicht-statische Member-Funktion ist, und die Ausdrücke a.*pmf und p->*pmf, wobei pmf ein Zeiger auf eine Member-Funktion ist, werden als prvalue-Ausdrücke klassifiziert, können aber nicht zur Initialisierung von Referenzen, als Funktionsargumente oder für irgendeinen Zweck verwendet werden, außer als linke Operand des Funktionsaufrufoperators, z. B. (p->*pmf)(args).

[editieren] 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 aber nicht zur Initialisierung von Referenzen oder als Funktionsargumente verwendet werden. Sie können in verworfenen Wertkontexten verwendet werden (z. B. auf einer eigenen Zeile, als linker Operand 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.

(seit C++17)

[editieren] Bit-Felder

Ein Ausdruck, der ein Bit-Feld bezeichnet (z. B. a.m, wobei a ein lvalue 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-const lvalue-Referenz kann nicht an ihn gebunden werden. Eine const lvalue-Referenz oder rvalue-Referenz kann von einem Bit-Feld-glvalue initialisiert werden, aber eine temporäre Kopie des Bit-Feldes wird erstellt: sie wird nicht direkt an das Bit-Feld gebunden.

Move-fähige Ausdrücke

Obwohl ein Ausdruck, der aus dem Namen einer beliebigen Variablen besteht, ein lvalue-Ausdruck ist, kann ein solcher Ausdruck move-fähig sein, wenn er als Operand eines

steht. Wenn ein Ausdruck move-fähig ist, wird er für den Zweck der Überladungsauflösung entweder als rvalue oder als lvalue behandelt(bis C++23)als rvalue behandelt(seit C++23) (wodurch er den Move-Konstruktor auswählen kann). Siehe Automatische Verschiebung von lokalen Variablen und Parametern für Details.

(seit C++11)

[editieren] Verlauf

[editieren] CPL

Die Programmiersprache CPL führte als erste Wertkategorien für Ausdrücke ein: Alle CPL-Ausdrücke können im "Rechtsmodus" ausgewertet werden, aber nur bestimmte Arten von Ausdrücken sind im "Linksmouds" sinnvoll. Bei der Auswertung im Rechtsmodus wird ein Ausdruck als Regel für die Berechnung eines Wertes betrachtet (der Rechtswert oder rvalue). Bei der Auswertung im Linksmodus gibt ein Ausdruck effektiv eine Adresse an (der Linkswert oder lvalue). "Links" und "Rechts" bezogen sich hier auf "links von der Zuweisung" und "rechts von der Zuweisung".

[editieren] C

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

[editieren] C++98

Das C++ vor 2011 folgte dem C-Modell, stellte aber den Namen "rvalue" für Nicht-lvalue-Ausdrücke wieder her, machte Funktionen zu lvalues und fügte die Regel hinzu, dass sich Referenzen an lvalues binden können, aber nur Referenzen zu const sich an rvalues binden können. Mehrere Nicht-lvalue-C-Ausdrücke wurden in C++ zu lvalue-Ausdrücken.

[editieren] 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]

  • Identität haben: Es ist möglich zu bestimmen, ob sich der Ausdruck auf dieselbe Entität bezieht wie ein anderer Ausdruck, z. B. durch den Vergleich von Adressen der identifizierten Objekte oder Funktionen (direkt oder indirekt erhalten);
  • kann verschoben werden: Move-Konstruktor, Move-Zuweisungsoperator oder eine andere Funktionsüberladung, die Move-Semantik implementiert, kann sich an den Ausdruck binden.

In C++11 sind Ausdrücke, die

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

Die Ausdrücke, die Identität haben, werden als "glvalue-Ausdrücke" bezeichnet (glvalue steht für "generalized lvalue"). Sowohl lvalues als auch xvalues sind glvalue-Ausdrücke.

Die Ausdrücke, die verschoben werden können, werden als "rvalue-Ausdrücke" bezeichnet. Sowohl prvalues als auch xvalues sind rvalue-Ausdrücke.

[editieren] C++17

In C++17 wurde Copy Elision in einigen Situationen obligatorisch, was die Trennung von prvalue-Ausdrücken von den temporären Objekten, die sie initialisieren, erforderte, was zum heutigen System führte. Beachten Sie, dass im Gegensatz zum C++11-Schema prvalues nicht mehr verschoben werden.

[editieren] Fußnoten

  1. Unter der Annahme, dass i einen eingebauten Typ hat oder der Präfix-Inkrement-Operator so überladen ist, dass er eine lvalue-Referenz zurückgibt.
  2. 2.0 2.1 2.2 2.3 Spezielle Rvalue-Kategorie, siehe ausstehender Member-Funktionsaufruf.
  3. Unter der Annahme, dass i einen integrierten Typ hat oder der Post-Inkrement-Operator nicht überladen ist, um per Lvalue-Referenz zurückzukehren.
  4. "Es gab eine Meinungsverschiedenheit innerhalb der C-Community bezüglich der Bedeutung von lvalue; eine Gruppe betrachtete ein lvalue als jede Art von Objektlokalisator, während eine andere Gruppe der Meinung war, dass ein lvalue auf der linken Seite eines Zuweisungsoperators sinnvoll ist. Das C89-Komitee übernahm die Definition von lvalue als Objektlokalisator." -- ANSI C Rationale, 6.3.2.1/10.
  5. "Neue" Wertterminologie von Bjarne Stroustrup, 2010.
  6. const prvalues (nur für Klassentypen zulässig) und const xvalues binden nicht an T&&-Überladungen, aber sie binden an die const T&&-Überladungen, die vom Standard ebenfalls als "Move-Konstruktor" und "Move-Zuweisungsoperator" klassifiziert werden und die Definition von "kann verschoben werden" für den Zweck dieser Klassifizierung erfüllen. Solche Überladungen können jedoch ihre Argumente nicht ändern und werden in der Praxis nicht verwendet; in ihrer Abwesenheit binden const prvalues und const xvalues an const T&-Überladungen.

[bearbeiten] 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]

[bearbeiten] 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 Mitgliedszugriff und Mitgliedszugriff über
Zeiger auf Mitglied eines Rvalues ergab Prvalue
wird als Xvalue neu klassifiziert
CWG 1059 C++11 Array-Prvalues konnten nicht CV-qualifiziert werden erlaubt
CWG 1213 C++11 Indizierung eines Array-Rvalues ergab Lvalue wird als Xvalue neu klassifiziert

[bearbeiten] Siehe auch

C-Dokumentation für Wertkategorien

[bearbeiten] Externe Links

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