Einige häufige Fälle, in denen die Bewertungsreihenfolge bisher nicht spezifiziert wurde , sind spezifiziert und gültig mit C++17
. Einige undefinierte Verhaltensweisen sind jetzt nicht mehr spezifiziert.
i = 1;
f(i++, i)
war undefiniert, ist aber jetzt nicht spezifiziert. Insbesondere wird nicht die Reihenfolge angegeben, in der jedes Argument f
relativ zu den anderen bewertet wird. i++
kann vorher ausgewertet i
werden oder umgekehrt. In der Tat kann ein zweiter Aufruf in einer anderen Reihenfolge ausgewertet werden, obwohl er sich unter demselben Compiler befindet.
Die Bewertung jedes Arguments muss jedoch vollständig mit allen Nebenwirkungen ausgeführt werden, bevor ein anderes Argument ausgeführt wird. So erhalten Sie möglicherweise f(1, 1)
(zweites Argument zuerst ausgewertet) oder f(1, 2)
(erstes Argument zuerst ausgewertet). Aber Sie werden nie f(2, 2)
etwas anderes von dieser Art bekommen.
std::cout << f() << f() << f();
wurde nicht angegeben, wird jedoch mit der Priorität des Operators kompatibel, sodass die erste Auswertung von f
im Stream an erster Stelle steht (Beispiele unten).
f(g(), h(), j());
hat noch eine nicht spezifizierte Bewertungsreihenfolge von g, h und j. Beachten Sie, dass für getf()(g(),h(),j())
die Regeln der Status angegeben getf()
wird, der zuvor ausgewertet wird g, h, j
.
Beachten Sie auch das folgende Beispiel aus dem Vorschlagstext:
std::string s = "but I have heard it works even if you don't believe in it"
s.replace(0, 4, "").replace(s.find("even"), 4, "only")
.replace(s.find(" don't"), 6, "");
Das Beispiel stammt aus der C ++ - Programmiersprache , 4. Ausgabe, Stroustrup, und war früher ein nicht spezifiziertes Verhalten, aber mit C ++ 17 funktioniert es wie erwartet. Es gab ähnliche Probleme mit wiederaufnehmbaren Funktionen ( .then( . . . )
).
Betrachten Sie als weiteres Beispiel Folgendes:
#include <iostream>
#include <string>
#include <vector>
#include <cassert>
struct Speaker{
int i =0;
Speaker(std::vector<std::string> words) :words(words) {}
std::vector<std::string> words;
std::string operator()(){
assert(words.size()>0);
if(i==words.size()) i=0;
// Pre-C++17 version:
auto word = words[i] + (i+1==words.size()?"\n":",");
++i;
return word;
// Still not possible with C++17:
// return words[i++] + (i==words.size()?"\n":",");
}
};
int main() {
auto spk = Speaker{{"All", "Work", "and", "no", "play"}};
std::cout << spk() << spk() << spk() << spk() << spk() ;
}
Mit C ++ 14 und früher können (und werden) wir Ergebnisse erhalten wie
play
no,and,Work,All,
anstatt
All,work,and,no,play
Beachten Sie, dass das oben Gesagte tatsächlich dasselbe ist wie
(((((std::cout << spk()) << spk()) << spk()) << spk()) << spk()) ;
Vor C ++ 17 gab es jedoch keine Garantie dafür, dass die ersten Aufrufe zuerst in den Stream gelangen.
Referenzen: Aus dem angenommenen Vorschlag :
Postfix-Ausdrücke werden von links nach rechts ausgewertet. Dies umfasst Funktionsaufrufe und Mitgliederauswahlausdrücke.
Zuweisungsausdrücke werden von rechts nach links ausgewertet. Dies schließt zusammengesetzte Zuordnungen ein.
Operanden zum Verschieben von Operatoren werden von links nach rechts ausgewertet. Zusammenfassend werden die folgenden Ausdrücke in der Reihenfolge a, dann b, dann c, dann d ausgewertet:
- ab
- a-> b
- a -> * b
- a (b1, b2, b3)
- b @ = a
- a [b]
- a << b
- a >> b
Darüber hinaus schlagen wir die folgende zusätzliche Regel vor: Die Reihenfolge der Auswertung eines Ausdrucks mit einem überladenen Operator wird durch die Reihenfolge bestimmt, die dem entsprechenden integrierten Operator zugeordnet ist, nicht durch die Regeln für Funktionsaufrufe.
Anmerkung bearbeiten: Meine ursprüngliche Antwort wurde falsch interpretiert a(b1, b2, b3)
. Die Reihenfolge der b1
, b2
, b3
ist noch nicht spezifiziert. (Danke @KABoissonneault, alle Kommentatoren.)
Jedoch (wie @Yakk weist darauf hin) , und das ist wichtig: Auch wenn b1
, b2
, b3
sind nicht-triviale Ausdrücke ist jeder von ihnen vollständig ausgewertet und an die jeweiligen Funktionsparameter gebunden , bevor die andere ausgewertet wird gestartet. Der Standard besagt dies folgendermaßen:
§5.2.2 - Funktionsaufruf 5.2.2.4:
. . . Der Postfix-Ausdruck wird vor jedem Ausdruck in der Ausdrucksliste und vor jedem Standardargument sequenziert. Jede mit der Initialisierung eines Parameters verbundene Wertberechnung und Nebenwirkung sowie die Initialisierung selbst werden vor jeder mit der Initialisierung eines nachfolgenden Parameters verbundenen Wertberechnung und Nebenwirkung sequenziert.
Einer dieser neuen Sätze fehlt jedoch im GitHub-Entwurf :
Jede mit der Initialisierung eines Parameters verbundene Wertberechnung und Nebenwirkung sowie die Initialisierung selbst werden vor jeder mit der Initialisierung eines nachfolgenden Parameters verbundenen Wertberechnung und Nebenwirkung sequenziert.
Das Beispiel ist da. Es löst ein jahrzehntealtes Problem ( wie von Herb Sutter erklärt ) mit Ausnahme der Sicherheit, wo Dinge wie
f(std::unique_ptr<A> a, std::unique_ptr<B> b);
f(get_raw_a(), get_raw_a());
würde auslaufen, wenn einer der Aufrufe get_raw_a()
ausgelöst würde, bevor der andere Rohzeiger an seinen Smart-Pointer-Parameter gebunden wurde.
Wie von TC hervorgehoben, ist das Beispiel fehlerhaft, da die Konstruktion von unique_ptr aus dem Rohzeiger explizit ist und dies das Kompilieren verhindert. *
Beachten Sie auch diese klassische Frage (markiert mit C , nicht mit C ++ ):
int x=0;
x++ + ++x;
ist noch undefiniert.