web-dev-qa-db-fra.com

Pourquoi l'opérateur ternaire avec des virgules n'évalue-t-il qu'une seule expression dans le cas réel?

J'apprends actuellement le C++ avec le livre C++ Primer et l'un des exercices du livre est:

Expliquez ce que fait l'expression suivante: someValue ? ++x, ++y : --x, --y

Que savons-nous? Nous savons que l'opérateur ternaire a une priorité plus élevée que l'opérateur virgule. Avec les opérateurs binaires, cela était assez facile à comprendre, mais avec l’opérateur ternaire, je me bats un peu. Avec les opérateurs binaires, "avoir une priorité plus élevée" signifie que nous pouvons utiliser des parenthèses autour de l'expression avec une priorité plus élevée et que cela ne modifiera pas l'exécution.

Pour l'opérateur ternaire je ferais:

(someValue ? ++x, ++y : --x, --y)

aboutissant effectivement au même code, ce qui ne m'aide pas à comprendre comment le compilateur va regrouper le code.

Cependant, en testant avec un compilateur C++, je sais que l'expression est compilée et je ne sais pas ce qu'un opérateur : Pourrait représenter par lui-même. Le compilateur semble donc interpréter correctement l'opérateur ternaire.

Ensuite, j'ai exécuté le programme de deux manières:

#include <iostream>

int main()
{
    bool someValue = true;
    int x = 10, y = 10;

    someValue ? ++x, ++y : --x, --y;

    std::cout << x << " " << y << std::endl;
    return 0;
}

Résulte en:

11 10

De l’autre côté avec someValue = false, Il affiche:

9 9

Pourquoi le compilateur C++ génère-t-il un code qui, pour la branche true de l'opérateur ternaire, incrémente uniquement x, alors que pour la branche false du ternaire, il décrémente à la fois x et y?

Je suis même allé jusqu'à mettre des parenthèses autour de la branche vraie comme ceci:

someValue ? (++x, ++y) : --x, --y;

mais il en résulte toujours 11 10.

117
Aufziehvogel

Comme @ Rakete a déclaré dans leur excellente réponse, c'est délicat. J'aimerais ajouter quelque chose à cela.

L'opérateur ternaire doit avoir la forme:

expression-logique? expression: Affectation-expression

Nous avons donc les mappages suivants:

  • someValue: expression-logique
  • ++x, ++y: expression
  • ??? is Affectation-expression--x, --y ou seulement --x?

En fait, ce n’est que --x parce que expression d'affectation ne peut pas être analysé comme deux expressions séparées par une virgule (selon les règles de grammaire de C++), donc --x, --y ne peut pas être traité comme un expression d'affectation.

La partie d'expression ternaire (conditionnelle) ressemble à ceci:

someValue?++x,++y:--x

Par souci de lisibilité, il peut être utile de considérer ++x,++y à calculer comme-si entre parenthèses (++x,++y); tout ce qui est contenu entre ? et : sera séquencé après le conditionnel. (Je les parenthèses pour le reste du post).

et évalué dans cet ordre:

  1. someValue?
  2. (++x,++y) ou --x _ (selon boolrésultat de 1.)

Cette expression est ensuite traitée comme la sous-expression gauche d'un opérateur de virgule, la sous-expression droite étant --y, ainsi:

(someValue?(++x,++y):--x), --y;

Ce qui signifie que le côté gauche est un expression rejetée, ce qui signifie qu'il est définitivement évalué, mais ensuite nous évaluons le côté droit et le renvoyons.

Alors que se passe-t-il quand someValue est true?

  1. (someValue?(++x,++y):--x) exécute et incrémente x et y pour être 11 et 11
  2. L'expression de gauche est ignorée (bien que les effets secondaires de l'incrément restent)
  3. Nous évaluons le côté droit de l'opérateur virgule: --y, qui décrémente ensuite y en 10

Pour "corriger" le comportement, vous pouvez grouper --x, --y avec des parenthèses pour le transformer en expression primaire qui est une entrée valide pour un assignation -expression *:

someValue?++x,++y:(--x, --y);

* C'est une longue chaîne plutôt amusante qui relie un Affectation-expression à une expression primaire:

Affectation-expression --- (peut être constitué de) -> expression-conditionnelle -> expression-logique -> expression-logique -> expression-ou-inclusive -> expression-ou-exclusive -> et-expression -> égalité-expression -> expression-relationnelle - > shift-expression -> expression-additive -> expression-multiplicative -> pm-expression -> expression-cast -> expression-unaire -> expression-postfix -> expression-primaire

121
AndyG

Wow, c'est délicat.

Le compilateur voit votre expression comme:

(someValue ? (++x, ++y) : --x), --y;

L'opérateur ternaire a besoin d'un :, il ne peut pas fonctionner seul dans ce contexte, mais après cela, il n’ya aucune raison pour que la virgule appartienne au faux cas.

Il serait peut-être plus logique que vous obteniez cette sortie. Si someValue est vrai, alors ++x, ++y et --y être exécuté, ce qui ne change pas efficacement y mais en ajoute un à x.

Si someValue est faux, alors --x et --y sont exécutés en les décrémentant de un.

87
Rakete1111

Pourquoi le compilateur C++ génère-t-il un code qui, pour la branche true de l'opérateur ternaire, incrémente uniquement x

Vous avez mal interprété ce qui s'est passé. La branche vraie incrémente x et y. Cependant, y est décrémenté immédiatement après, sans condition.

Voici comment cela se produit: puisque l'opérateur conditionnel a une priorité supérieure à l'opérateur virgule en C++ , le compilateur analyse l'expression comme suit:

   (someValue ? ++x, ++y : --x), (--y);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^  ^^^^^

Notez le "orphelin" --y après la virgule. C'est ce qui conduit à décrémenter y qui a été initialement incrémenté.

Je suis même allé jusqu'à mettre des parenthèses autour de la branche vraie comme ceci:

someValue ? (++x, ++y) : --x, --y;

Vous étiez sur le bon chemin, mais vous avez mis entre parenthèses une branche incorrecte: vous pouvez résoudre ce problème en mettant entre parenthèses la branche else, comme ceci:

someValue ? ++x, ++y : (--x, --y);

démo (empreintes 11 11)

42
dasblinkenlight

Votre problème est que l'expression ternaire n'a pas de priorité plus élevée que la virgule. En fait, le C++ ne peut pas être décrit avec précision simplement par la priorité - et c’est exactement l’interaction entre l’opérateur ternaire et la virgule où il tombe en panne.

a ? b++, c++ : d++

est traité comme:

a ? (b++, c++) : d++

(la virgule se comporte comme si elle avait une priorité plus élevée). D'autre part,

a ? b++ : c++, d++

est traité comme:

(a ? b++ : c++), d++

et l'opérateur ternaire est une priorité supérieure.

5
Martin Bonner

Un point qui a été négligé dans les réponses (bien que mentionné dans les commentaires) est que l'opérateur conditionnel est invariablement utilisé (prévu par la conception?) Dans le code réel comme raccourci pour affecter l'une des deux valeurs à une variable.

Ainsi, le contexte plus large serait:

whatIreallyWanted = someValue ? ++x, ++y : --x, --y;

Ce qui est absurde à première vue, alors les crimes sont multiples:

  • La langue permet des effets secondaires ridicules dans une mission.
  • Le compilateur ne vous a pas averti que vous faisiez des choses bizarres.
  • Le livre semble se concentrer sur des questions "astuces". On peut seulement espérer que la réponse à l’arrière était "Ce que cette expression fait, c’est compter sur des cas étranges dans Edge, dans un exemple artificiel, pour produire des effets secondaires auxquels personne ne s’attend. Ne faites jamais cela.
2
Taryn