Je lisais cette question:
Comportement indéfini et points de séquence
et, plus précisément, la réponse C++ 11 , et je comprends l'idée de "séquencer" les évaluations. Mais - y a-t-il un séquençage suffisant lorsque j'écris:
f(x++), g(x++);
?
Autrement dit, ai-je la garantie que f()
obtient la valeur d'origine de x
et g()
obtient un x
une fois incrémenté?
Notes pour les nitpickers:
operator++()
a un comportement défini (même si nous l'avons remplacé) et ainsi f()
et g()
, qu'aucune exception ne sera levée, etc. - cette question est pas à ce sujet.operator,()
n'a pas été surchargé.Non, le comportement est défini. Pour citer C++ 11 (n3337) [expr.comma/1] :
Une paire d'expressions séparées par une virgule est évaluée de gauche à droite; l'expression de gauche est une expression de valeur rejetée (clause [expr]). Chaque calcul de valeur et effet secondaire associé à l'expression de gauche est séquencé avant chaque calcul de valeur et effet secondaire associé à l'expression de droite.
Et je prends "chaque" pour signifier "chaque"1. L'évaluation du second x++
ne peut pas se produire avant que la séquence d'appel à f
ne soit terminée et que f
revienne.2
1 Les appels de destructeur ne sont pas associés à des sous-expressions, mais uniquement à des expressions complètes. Vous verrez donc ceux exécutés dans l'ordre inverse de la création d'objet temporaire à la fin de l'expression complète.
2 Ce paragraphe s'applique uniquement à la virgule lorsqu'elle est utilisée comme opérateur. Lorsque la virgule a une signification spéciale (comme lors de la désignation d'une séquence d'arguments d'appel de fonction), cela ne s'applique pas.
Selon cet ordre d'évaluation et référence de séquençage le côté gauche de la virgule est entièrement évalué avant le côté droit (voir règle 9):
9) Chaque calcul de valeur et effet secondaire du premier argument (gauche) de l'opérateur de virgule intégré est séquencé avant chaque calcul de valeur et effet secondaire du deuxième argument (droite).
Cela signifie qu'une expression comme f(x++), g(x++)
est pas indéfinie.
Notez que cela n'est valable que pour l'opérateur intégré virgule.
Tout d'abord, supposons que x++
N'invoque pas en soi un comportement indéfini. Pensez au débordement signé, à l'incrémentation d'un pointeur passé la fin, ou l'opérateur postfix-increment peut être défini par l'utilisateur).
Supposons en outre qu'invoquer f()
et g()
avec leurs arguments et détruire les temporaires n'invoque pas un comportement indéfini.
Ce sont beaucoup d'hypothèses, mais si elles sont brisées, la réponse est triviale.
Maintenant, si la virgule est l'opérateur de virgule intégré, la virgule dans une liste d'initiation contreventée ou la virgule dans une liste d'initialisation mem, les côtés gauche et droit sont séquencés avant ou après l'autre (et vous savez lequel), alors n'intervenez pas, ce qui rend le comportement bien défini.
struct X {
int f, g;
explicit X(int x) : f(x++), g(x++) {}
};
// Demonstrate that the order depends on member-order, not initializer-order:
struct Y {
int g, f;
explicit Y(int x) : f(x++), g(x++) {}
};
int y[] = { f(x++), g(x++) };
Sinon, si x++
Appelle une surcharge d'opérateur définie par l'utilisateur pour l'incrément postfix, vous avez un séquencement indéterminé des deux instances de x++
Et donc un comportement non spécifié.
std::list<int> list{1,2,3,4,5,6,7};
auto x = begin(list);
using T = decltype(x);
void h(T, T);
h(f(x++), g(x++));
struct X {
X(T, T) {}
}
X(f(x++), g(x++));
Et dans le dernier cas, vous obtenez un comportement non défini complet car les deux incréments postfix de x
ne sont pas séquencés.
int x = 0;
void h(int, int);
h(f(x++), g(x++));
struct X {
X(int, int) {}
}
X(f(x++), g(x++));