Wertkategorien
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
decltypeist;
- 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 Führen Sie diesen Code aus #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. Führen Sie diesen Code aus #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
- der Name einer Variablen, einer Funktion, eines nicht-Typ-Templateparameters(seit C++20) oder eines Datenmembers, unabhängig vom Typ, wie z. B. std::cin oder std::endl. Selbst wenn der Typ der Variablen eine Rvalue-Referenz ist, ist der Ausdruck, der aus ihrem Namen besteht, ein lvalue-Ausdruck (aber siehe Move-fähige 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 } |
- a = b, a += b, a %= b und alle anderen eingebauten Zuweisungs- und zusammengesetzten Zuweisungsausdrücke;
- ++a und --a, die eingebauten Präfix-Inkrement- und Präfix-Dekrementausdrücke;
- *p, der eingebaute Indirektionsoperator;
- a[n] und p[n], die eingebauten Subscript-Operatoren, wobei ein Operand in a[n] ein Array-lvalue ist(seit C++11);
- a.m, der Member-of-Object-Ausdruck, außer wenn
mein Enumerator-Member oder eine nicht-statische Member-Funktion ist, oder wenn a ein rvalue ist undmein nicht-statisches Datenmember vom Objekttyp 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 } |
- p->m, der eingebaute Member-of-Pointer-Ausdruck, außer wenn
mein Enumerator-Member oder eine nicht-statische Member-Funktion ist; - a.*mp, der Pointer-to-Member-of-Object-Ausdruck, wobei a ein lvalue ist und
mpein Zeiger auf ein Datenmember ist; - p->*mp, der eingebaute Pointer-to-Member-of-Pointer-Ausdruck, wobei
mpein Zeiger auf ein Datenmember ist; - a, b, der eingebaute Kommaoperator-Ausdruck, wobei b ein lvalue ist;
- a ? b : c, der bedingte Operator für bestimmte b und c (z. B. wenn beide lvalues vom selben Typ sind, aber siehe Definition für Details);
- ein Zeichenkettenliteral, wie z. B. "Hello, world!";
- ein Cast-Ausdruck zu einem lvalue-Referenztyp, wie z. B. static_cast<int&>(x) oder static_cast<void(&)(int)>(x);
- ein nicht-Typ- Templateparameter eines lvalue-Referenztyps;
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>(); }
|
(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
- ein Literal (außer für Zeichenkettenliterale), wie z. B. 42, true oder nullptr;
- ein Funktionsaufruf oder ein überladener Operator-Ausdruck, dessen Rückgabetyp keine Referenz ist, wie z. B. str.substr(1, 2), str1 + str2 oder it++;
- a++ und a--, die eingebauten Post-Inkrement- und Post-Dekrementausdrücke;
- a + b, a % b, a & b, a << b und alle anderen eingebauten arithmetischen Ausdrücke;
- a && b, a || b, !a, die eingebauten logischen Ausdrücke;
- a < b, a == b, a >= b und alle anderen eingebauten Vergleichsoperatoren;
- &a, der eingebaute Adressoperator;
- a.m, der Member-of-Object-Ausdruck, wobei
mein Enumerator-Member oder eine nicht-statische Member-Funktion ist[2]; - p->m, der eingebaute Member-of-Pointer-Ausdruck, wobei
mein Enumerator-Member oder eine nicht-statische Member-Funktion ist[2]; - a.*mp, der Pointer-to-Member-of-Object-Ausdruck, wobei
mpein Zeiger auf eine Member-Funktion ist[2]; - p->*mp, der eingebaute Pointer-to-Member-of-Pointer-Ausdruck, wobei
mpein Zeiger auf eine Member-Funktion ist[2]; - a, b, der eingebaute Kommaoperator-Ausdruck, wobei b ein prvalue ist;
- a ? b : c, der bedingte Operator für bestimmte b und c (siehe Definition für Details);
- ein Cast-Ausdruck zu einem Nicht-Referenztyp, wie z. B. static_cast<double>(x), std::string{} oder (int)42;
- der
this-Zeiger; - ein Enumerator;
- ein nicht-Typ- Templateparameter eines Skalar-Typs;
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
- a.m, der Member-of-Object-Ausdruck, wobei a ein rvalue ist und
mein nicht-statisches Datenmember eines Objekttyps ist; - a.*mp, der Pointer-to-Member-of-Object-Ausdruck, wobei a ein rvalue ist und
mpein Zeiger auf ein Datenmember ist; - a, b, der eingebaute Kommaoperator-Ausdruck, wobei b ein xvalue ist;
- a ? b : c, der bedingte Operator für bestimmte b und c (siehe Definition für Details);
|
(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 |
|---|
|
Führen Sie diesen Code aus #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.
|
(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ückeObwohl 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
- ↑ 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.0 2.1 2.2 2.3 Spezielle Rvalue-Kategorie, siehe ausstehender Member-Funktionsaufruf.
- ↑ Unter der Annahme, dass i einen integrierten Typ hat oder der Post-Inkrement-Operator nicht überladen ist, um per Lvalue-Referenz zurückzukehren.
- ↑ "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.
- ↑ "Neue" Wertterminologie von Bjarne Stroustrup, 2010.
- ↑ 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 |