AKTUALISIEREN: Nach der Diskussion in den Kommentaren habe ich am Ende dieser Antwort weitere Beweise hinzugefügt.
Haftungsausschluss: Ich gebe zu, diese Antwort ist ziemlich spekulativ. Die aktuelle Formulierung des C++11-Standards hingegen scheint eine formellere Antwort nicht zuzulassen.
Im Zusammenhang mit diese Fragen und Antwortenhat sich herausgestellt, dass der C++11-Standard nicht formal spezifiziert, was Wertkategorien werden von jedem Sprachkonstrukt erwartet. Im Folgenden werde ich mich hauptsächlich darauf konzentrieren eingebaute Operatorenobwohl es um die Frage geht Initialisierer. Schließlich werde ich die Schlussfolgerungen, die ich für den Fall der Operatoren gezogen habe, auf den Fall der Initialisierer ausdehnen.
Bei eingebauten Operatoren finden sich trotz fehlender formaler Spezifikation (nicht normative) Hinweise in der Norm, dass die vorgesehen Spezifikation ist zu Lassen Sie prvalues erwartet werden, wo immer ein Wert benötigt wird und wenn nicht anders angegeben.
Beispielsweise heißt es in einer Anmerkung in Absatz 3.10/1:
Die Diskussion jedes eingebauten Operators in Abschnitt 5 gibt die Kategorie des Werts an, den er liefert, und die Wertkategorien der Operanden, die er erwartet. Zum Beispiel, Die eingebauten Zuweisungsoperatoren erwarten, dass der linke Operand ein lvalue und der rechte Operand ein prvalue ist und liefern als Ergebnis einen lvalue. Benutzerdefinierte Operatoren sind Funktionen, und die Kategorien von Werten, die sie erwarten und zurückgeben, werden durch ihre Parameter und Rückgabetypen bestimmt
Abschnitt 5.17 zu Zuweisungsoperatoren hingegen erwähnt dies nicht. Die Möglichkeit, eine lvalue-to-rvalue-Konvertierung durchzuführen, wird jedoch erneut in einem Hinweis (Absatz 5.17/1) erwähnt:
Daher darf ein Funktionsaufruf nicht zwischengeschaltet werden die lvalue-zu-rvalue-Konvertierung und die Nebenwirkung, die mit jedem einzelnen zusammengesetzten Zuweisungsoperator verbunden ist
Wenn kein rvalue erwartet würde, wäre dieser Hinweis natürlich bedeutungslos.
Ein weiterer Beweis findet sich in 4/8, wie Johannes Schaub in den Kommentaren zu verlinkten Fragen und Antworten darauf hingewiesen hat:
Es gibt einige Kontexte, in denen bestimmte Konvertierungen unterdrückt werden. Beispielsweise wird die lvalue-zu-rvalue-Konvertierung nicht für den Operanden des unären &-Operators durchgeführt. Spezifische Ausnahmen sind in den Beschreibungen dieser Operatoren und Kontexte angegeben.
Dies scheint zu implizieren, dass die lvalue-zu-rvalue-Konvertierung für alle Operanden der integrierten Operatoren durchgeführt wird, sofern nicht anders angegeben. Das würde wiederum das bedeuten rvalues werden als Operanden von integrierten Operatoren erwartet, sofern nicht anders angegeben.
VERMUTUNG:
Auch wenn die Initialisierung keine Zuweisung ist und daher Operatoren nicht in die Diskussion eintreten, ist mein Verdacht, dass dieser Bereich der Spezifikation von genau dem oben beschriebenen Problem betroffen ist.
Spuren, die diesen Glauben stützen, finden sich sogar in Abschnitt 8.5.2/5 über die Initialisierung von Verweise (für den der Wert des lvalue-Initialisierungsausdrucks nicht benötigt wird):
Die üblich lvalue-to-rvalue (4.1), array-to-pointer (4.2) und function-to-pointer (4.3) Standardkonvertierungen sind nicht erforderlich und werden daher unterdrückt, wenn solche direkten Bindungen an lvalues vorgenommen werden.
Das Wort “üblich” scheint anzudeuten, dass bei der Initialisierung von Objekten, die nicht von einem Referenztyp sind, eine lvalue-zu-rvalue-Konvertierung angewendet werden soll.
Daher glaube ich, dass, obwohl Anforderungen an die Erwartungswertkategorie von Initialisierern schlecht spezifiziert sind (wenn nicht sogar vollständig fehlen), es aufgrund der bereitgestellten Beweise sinnvoll ist anzunehmen, dass die vorgesehen Spezifikation ist, dass:
Überall dort, wo ein Wert von einem Sprachkonstrukt benötigt wird, wird ein Prvalue erwartet, sofern nicht anders angegeben.
Unter dieser Annahme wäre in Ihrem Beispiel eine lvalue-to-rvalue-Konvertierung erforderlich, was zu undefiniertem Verhalten führen würde.
ZUSÄTZLICHE BEWEISE:
Nur um weitere Beweise zu liefern, die diese Vermutung stützen, lassen Sie uns davon ausgehen falschsodass für die Kopierinitialisierung tatsächlich keine lvalue-zu-rvalue-Konvertierung erforderlich ist, und betrachten Sie den folgenden Code (danke an jogojapan für den Beitrag):
int y;
int x = y; // No UB
short t;
int u = t; // UB! (Do not like this non-uniformity, but could accept it)
int z;
z = x; // No UB (x is not uninitialized)
z = y; // UB! (Assuming assignment operators expect a prvalue, see above)
// This would be very counterintuitive, since x == y
Dieses ungleichmäßige Verhalten ergibt für mich nicht viel Sinn. Was meiner Meinung nach sinnvoller ist, ist, dass überall dort, wo ein Wert erforderlich ist, ein Prvalue erwartet wird.
Darüber hinaus ist, wie Jesse Good in seiner Antwort zu Recht feststellt, der Schlüsselabsatz des C++-Standards 8.5/16:
— Andernfalls ist der Anfangswert des Objekts, das initialisiert wird, der
(möglicherweise konvertierter) Wert des Initialisierungsausdrucks. Standardkonvertierungen (Klausel 4) werden verwendet, im Bedarfsfallum den Initialisierungsausdruck in die CV-unqualifizierte Version des Ziels zu konvertieren Art; Es werden keine benutzerdefinierten Konvertierungen berücksichtigt. Wenn die Konvertierung nicht durchgeführt werden kann, ist die Initialisierung falsch formatiert. [ Note:
An expression of type “cv1 T” can initialize an object of type “cv2 T”
independently of the cv-qualifiers cv1 and cv2.
However, while Jesse mainly focuses on the “if necessary” bit, I would also like to stress the word “type“. The paragraph above mentions that standard conversions will be used “if necessary” to convert to the destination type, but does not say anything about category conversions:
- Will category conversions be performed if needed?
- Are they needed?
For what concerns the second question, as discussed in the original part of the answer, the C++11 Standard currently does not specify whether category conversions are needed or not, because nowhere it is mentioned whether copy-initialization expects a prvalue as an initializer. Thus, a clear-cut answer is impossible to give. However, I believe I provided enough evidence to assume this to be the intended specification, so that the answer would be “Yes”.
As for the first question, it seems reasonable to me that the answer is “Yes” as well. If it were “No”, obviously correct programs would be ill-formed:
int y = 0;
int x = y; // y is lvalue, prvalue expected (assuming the conjecture is correct)
To sum it up (A1 = “Answer to question 1“, A2 = “Answer to question 2“):
| A2 = Yes | A2 = No |
---------|------------|---------|
A1 = Yes | UB | No UB |
A1 = No | ill-formed | No UB |
---------------------------------
If A2 is “No”, A1 does not matter: there’s no UB, but the bizarre situations of the first example (e.g. z = y
giving UB, but not z = x
even though x == y
) show up. If A2 is “Yes”, on the other hand, A1 becomes crucial; yet, enough evidence has been given to prove it would be “Yes”.
Therefore, my thesis is that A1 = “Yes” and A2 = “Yes”, and we should have Undefined Behavior.
FURTHER EVIDENCE:
This defect report (courtesy of Jesse Good) proposes a change that is aimed at giving Undefined Behavior in this case:
[…] Außerdem 4.1 [conv.lval] Absatz 1 besagt, dass die Anwendung der lvalue-zu-rvalue-Konvertierung auf ein „object [that] ist nicht initialisiert“ führt zu undefiniertem Verhalten; dies sollte in Begriffen eines Objekts mit einem unbestimmten Wert umformuliert werden.
Insbesondere lautet der vorgeschlagene Wortlaut für Absatz 4.1:
Wenn eine lvalue-zu-rvalue-Konvertierung in einem nicht ausgewerteten Operanden oder einem Unterausdruck davon erfolgt (Abschnitt 5 [expr]) wird nicht auf den im referenzierten Objekt enthaltenen Wert zugegriffen. In allen anderen Fällen wird das Ergebnis der Umrechnung nach folgenden Regeln ermittelt:
— Wenn T (möglicherweise CV-qualifiziert) std::nullptr_t ist, ist das Ergebnis eine Nullzeigerkonstante (4.10 [conv.ptr]).
— Andernfalls, wenn der glvalue T einen Klassentyp hat, kopiert die Konvertierung ein temporäres vom Typ T aus dem glvalue und das Ergebnis der Konvertierung ist ein prvalue für das temporäre.
— Andernfalls, wenn das Objekt, auf das sich der glvalue bezieht, einen ungültigen Zeigerwert enthält (3.7.4.2 [basic.stc.dynamic.deallocation]3.7.4.3 [basic.stc.dynamic.safety]), ist das Verhalten implementierungsdefiniert.
— Andernfalls, wenn T ein (möglicherweise CV-qualifizierter) Zeichentyp ohne Vorzeichen ist (3.9.1 [basic.fundamental]), und das Objekt, auf das sich der glvalue bezieht, enthält einen unbestimmten Wert (5.3.4 [expr.new]8.5 [dcl.init]12.6.2 [class.base.init]) und dieses Objekt keine automatische Speicherdauer hat oder der glvalue der Operand eines unären &-Operators war oder an eine Referenz gebunden war, ist das Ergebnis ein nicht spezifizierter Wert. [Footnote: The value may be different each time the lvalue-to-rvalue conversion is applied to the object. An unsigned char object with indeterminate value allocated to a register might trap. —end footnote]
— Andernfalls, wenn das Objekt, auf das sich der glvalue bezieht, einen unbestimmten Wert enthält, ist das Verhalten undefiniert.
— Andernfalls, wenn der glvalue den (möglicherweise CV-qualifizierten) Typ std::nullptr_t hat, ist das prvalue-Ergebnis eine Nullzeigerkonstante (4.10 [conv.ptr]). Andernfalls ist der Wert, der in dem durch glvalue angegebenen Objekt enthalten ist, das prvalue-Ergebnis.
Eine implizite Konvertierungssequenz eines Ausdrucks e
tippen T
ist als äquivalent zu der folgenden Deklaration definiert, using t
als Ergebnis der Konvertierung (Modulo-Wertkategorie, die abhängig von definiert wird T
), 4p3 und 4p6
T t = e;
Die Auswirkung einer impliziten Konvertierung ist die gleiche wie die Durchführung der entsprechenden Deklaration und Initialisierung und die anschließende Verwendung der temporären Variablen als Ergebnis der Konvertierung.
In Klausel 4 liefert die Umwandlung eines Ausdrucks in einen Typ immer Ausdrücke mit einer bestimmten Eigenschaft. Zum Beispiel die Konvertierung von 0
zu int*
ergibt einen Null-Zeigerwert und nicht nur einen beliebigen Zeigerwert. Auch die Wertekategorie ist eine spezifische Eigenschaft eines Ausdrucks und ihr Ergebnis ist wie folgt definiert
Das Ergebnis ist ein lvalue, wenn T ein lvalue-Referenztyp oder eine rvalue-Referenz auf den Funktionstyp (8.3.2) ist, ein xvalue, wenn T eine rvalue-Referenz auf den Objekttyp ist, und andernfalls ein prvalue.
Daher wissen wir das in int t = e;
das Ergebnis der Konvertierungssequenz ist ein Prvalue, weil int
ist ein Nicht-Referenztyp. Wenn wir also einen glvalue angeben, benötigen wir offensichtlich eine Konvertierung. 3.10p2 verdeutlicht das weiter, um keinen Zweifel zu lassen
Immer wenn ein glvalue in einem Kontext erscheint, in dem ein prvalue erwartet wird, wird der glvalue in einen prvalue umgewandelt; siehe 4.1, 4.2 und 4.3.
Ich habe das Gefühl, dass das Verhalten ziemlich definiert ist. Der Wert von
x
wird sich nicht ändern. Der Wert vonx
ist jedoch undefiniert.– Bingo
18. Februar 2013 um 12:55 Uhr
@Bingo: Wenn du das denkst, kannst du ein aus dem Sprachstandard abgeleitetes Argument formulieren und es als Antwort posten? 🙂
– Kerrek SB
18. Februar 2013 um 12:56 Uhr
Ist es nicht dasselbe wie zu fragen, ob
int y; int x = y;
ist UB? [edit: Hm, probably no. This is about computing the value of an unitialized variable, not a default-initialized one]– Andy Prowl
18. Februar 2013 um 13:22 Uhr
Ich frage mich, ob die Lebensdauer von
x
begann notwendigerweise in dem Moment, in dem es auf der rechten Seite bewertet wurde. Ist angegeben, dass der Teil auf der linken Seite (der meiner Meinung nach Speicher fürx
) wird vor dem Teil auf der rechten Seite sequenziert?– Andy Prowl
18. Februar 2013 um 22:24 Uhr
@AndyProwl: Die automatische Speicherzuweisung ist überhaupt nicht sequenziert. Nur Ausdrucksauswertung ist etwas, das sequenziert werden kann. Eine Deklarationsanweisung ist kein Ausdruck.
– Kerrek SB
18. Februar 2013 um 23:14 Uhr