Certains cas courants où l'ordre d'évaluation n'a jusqu'à présent pas été spécifié, sont spécifiés et valides avec C++17
. Certains comportements indéfinis sont maintenant plutôt non spécifiés.
i = 1;
f(i++, i)
était indéfini, mais il est maintenant non spécifié. Plus précisément, ce qui n'est pas spécifié est l'ordre dans lequel chaque argument de f
est évalué par rapport aux autres. i++
pourrait être évalué avant i
, ou vice versa. En effet, il pourrait évaluer un deuxième appel dans un ordre différent, malgré le même compilateur.
Cependant, l'évaluation de chaque argument est nécessaire pour être exécutée complètement, avec tous les effets secondaires, avant l'exécution de tout autre argument. Ainsi, vous pouvez obtenir f(1, 1)
(deuxième argument évalué en premier) ou f(1, 2)
(premier argument évalué en premier). Mais vous n'obtiendrez jamais f(2, 2)
ou quelque chose de similaire.
std::cout << f() << f() << f();
était non spécifié, mais il deviendra compatible avec la précédence des opérateurs de sorte que la première évaluation de f
viendra en premier dans le flux (exemples ci-dessous).
f(g(), h(), j());
garde l'ordre non spécifié d'évaluation de g, h et j. Notez que pour getf()(g(),h(),j())
, les règles indiquent que getf()
sera évalué avant g, h, j
.
Notez également l'exemple suivant tiré du texte de proposition :
std::string s = "mais j'ai entendu dire que ça marche même si vous n'y croyez pas"
s.replace(0, 4, "").replace(s.find("même"), 4, "seulement")
.replace(s.find(" ne croyez"), 6, "");
L'exemple provient de Le langage de programmation C++, 4ème édition, Stroustrup, et c'était un comportement non spécifié, mais avec C++17, cela fonctionnera comme prévu. Il y avait des problèmes similaires avec les fonctions reprises (.then( . . . )
).
Par exemple, considérez ce qui suit :
#include
#include
#include
#include
struct Orateur{
int i =0;
Orateur(std::vector mots) :mots(mots) {}
std::vector mots;
std::string operator()(){
assert(mots.size()>0);
if(i==mots.size()) i=0;
// Version pré-C++17 :
auto mot = mots[i] + (i+1==mots.size()?"\n":",");
++i;
return mot;
// Toujours pas possible avec C++17 :
// return mots[i++] + (i==mots.size()?"\n":",");
}
};
int main() {
auto orateur = Orateur{{"Tout", "Travail", "et", "pas de", "jeu"}};
std::cout << orateur() << orateur() << orateur() << orateur() << orateur() ;
}
Avec C++14 et antérieur, nous pouvons (et allons) obtenir des résultats tels que
jeu
pas,et,Travail,Tout,
au lieu de
Tout,travail,et,pas,jeu
Notez que ce qui précède équivaut en fait à
(((((std::cout << orateur()) << orateur()) << orateur()) << orateur()) << orateur()) ;
Mais encore, avant C++17, il n'y avait aucune garantie que les premiers appels viendraient d'abord dans le flux.
Références: D'après la proposition acceptée:
Les expressions postfixées sont évaluées de gauche à droite. Cela inclut les appels de fonctions et les expressions de sélection de membres.
Les expressions d'assignation sont évaluées de droite à gauche. Cela inclut les affectations composées.
Les opérandes des opérateurs de décalage sont évalués de gauche à droite. En résumé, les expressions suivantes sont évaluées dans l'ordre a, puis b, puis c, puis d:
- a.b
- a->b
- a->*b
- a(b1, b2, b3)
- b @= a
- a[b]
- a << b
- a >> b
De plus, nous suggérons la règle supplémentaire suivante: l'ordre d'évaluation d'une expression impliquant un opérateur surchargé est déterminé par l'ordre associé à l'opérateur natif correspondant, et non par les règles des appels de fonction.
Remarque de modification : Ma réponse originale a mal interprété a(b1, b2, b3)
. L'ordre de b1
, b2
, b3
Cependant, (comme le souligne @Yakk) et c'est important : Même lorsque b1
, b2
, b3
sont des expressions non triviales, chacune d'elles est complètement évaluée et liée au paramètre de la fonction respectivement avant que les autres ne commencent à être évaluées. La norme le stipule ainsi :
§5.2.2 - Appel de fonction 5.2.2.4 :
. . . L'expression postfixée est séquencée avant chaque expression dans la liste d'expressions et tout argument par défaut. Chaque calcul de valeur et effet secondaire associé à l'initialisation d'un paramètre, et la propagation elle-même, est séquencé avant chaque calcul de valeur et effet secondaire associé à l'initialisation de tout paramètre subsequent.
Cependant, une de ces nouvelles phrases manque dans le brouillon GitHub:
Chaque calcul de valeur et effet secondaire associé à l'initialisation d'un paramètre, et l'initialisation elle-même, est séquencé avant chaque calcul de valeur et effet secondaire associé à l'initialisation de tout paramètre ultérieur.
L'exemple y est là. Cela résout un problème ancien de décennies (comme expliqué par Herb Sutter) avec la sécurité des exceptions où des choses comme
f(std::unique_ptr a, std::unique_ptr b);
f(get_raw_a(), get_raw_a());
**
fuiraient si l'un des appels get_raw_a()
lançait une exception avant l'autre pointeur brut était lié à son paramètre de pointeur intelligent.
Comme l'a souligné T.C., l'exemple est erroné car la construction de unique_ptr à partir d'un pointeur brut est explicite, empêchant cela de compiler.*
Remarquez également cette question classique (étiquetée C, pas C++) :
int x=0;
x++ + ++x;
est toujours indéfini.
**
0 votes
Lié à l'ordre d'évaluation de l'instruction d'assignation en C++ et Ce code du "Langage de programmation C++" 4ème édition section 36.3.6 a-t-il un comportement bien défini? qui sont tous deux traités dans le document. Le premier pourrait constituer un bel exemple supplémentaire dans votre réponse ci-dessous.
0 votes
Aussi quelque peu pertinent : l'ordre d'évaluation c++17 avec des fonctions de surcharge d'opérateur.
0 votes
précédence de l'opérateur n'est pas pertinente.