34 votes

Confusion avec les virgules dans une expression ternaire

J'ai trouvé le code intéressant suivant aujourd'hui :

SomeFunction(some_bool_variable ? 12.f, 50.f : 50.f, 12.f)

J'ai créé un petit échantillon pour reproduire le comportement :

class Vector3f
{
public:
    Vector3f(float val)
    {
        std::cout << "vector constructor: " << val << '\n';
    }
};

void SetSize(Vector3f v)
{
    std::cout << "SetSize single param\n";
}

void SetSize(float w, float h, float d=0)
{
    std::cout << "SetSize multi param: " << w << ", " << h << ", " << d << '\n';
}

int main()
{
    SetSize(true ? 12.f, 50.f : 50.f, 12.f);
    SetSize(false ? 12.f, 50.f : 50.f, 12.f);
}

( Échantillon en direct )

Le résultat que j'obtiens en exécutant le code ci-dessus est :

clang++ -std=c++14 -O2 -Wall -pedantic -lboost_system -lboost_filesystem -pthread main.cpp && ./a.out
main.cpp:29:20: warning: expression result unused [-Wunused-value]
    SetSize(true ? 12.f, 50.f : 50.f, 12.f);
                   ^~~~
main.cpp:30:21: warning: expression result unused [-Wunused-value]
    SetSize(false ? 12.f, 50.f : 50.f, 12.f);
                    ^~~~
2 warnings generated.
SetSize multi param: 50, 12, 0
SetSize multi param: 50, 12, 0

Ce que j'attendais en les deux était qu'un seul paramètre était passé à l'adresse suivante SetSize(float) . Cependant, deux paramètres sont passés, ce qui est extrêmement déroutant (d'autant plus que le ternaire a la priorité sur la virgule ; j'ai donc supposé que la virgule ne délimitait pas les arguments de fonction dans ce cas). Par exemple, si l'on utilise true le ternaire devrait donner 12.f, 50.f . Dans cette expression, la valeur située à gauche de la virgule est abandonnée/ignorée, de sorte que le résultat final devrait être le suivant :

SetSize(50.f);

La deuxième partie de la confusion est que si nous utilisons true o false dans le ternaire, les 2 mêmes valeurs sont passées à la fonction. Le site true le cas doit être h=12, w=50 Je pense...

Je vois que le compilateur essaie de m'avertir de quelque chose, mais je n'arrive pas à comprendre ce qui se passe. Quelqu'un peut-il décomposer cette logique et expliquer le résultat étape par étape ?

1 votes

Vous ne pouvez pas fournir de listes d'arguments avec l'opérateur ternaire. Il ne sélectionne que des valeurs.

6 votes

@EJP : Utilisons la section des réponses pour répondre aux questions. Merci.

0 votes

La grammaire du langage pour l'opérateur conditionnel ne permet pas d'utiliser une expression avec virgule pour la dernière partie (afin d'éviter toute ambiguïté).

30voto

dbush Points 8590

Si la deuxième partie de l'opérateur ternaire est autonome, la troisième partie ne l'est pas. La grammaire est la suivante :

Expression conditionnelle :

Expression logique

expression-or logique ? expression : expression d'affectation

Donc votre appel de fonction est effectivement ceci :

SetSize((true ? (12.f, 50.f): 50.f), 12.f)

Donc l'expression ternaire true ? (12.f, 50.f): 50.f est évalué comme le premier paramètre de la fonction. Ensuite, 12.f est transmise comme deuxième valeur. Dans ce cas, la virgule est no l'opérateur virgule mais le séparateur de paramètres de fonction.

Extrait de la section 5.18 du Norme C++ :

2 Dans les contextes où la virgule a une signification particulière, [ Exemple : dans des listes d'arguments de fonctions (5.2.2) et des listes d'initialisateurs (8.5) - exemple de fin ] l'opérateur virgule comme décrit dans la clause 5 ne peut apparaître que dans les parenthèses. [ Exemple :

f(a, (t=3, t+2), c);

a trois arguments, dont le deuxième a la valeur 5. - fin exemple ]

Si vous voulez que les deux dernières sous-expressions soient regroupées, vous devez ajouter des parenthèses :

SetSize(true ? 12.f, 50.f : (50.f, 12.f));
SetSize(false ? 12.f, 50.f : (50.f, 12.f));

Maintenant, vous avez un opérateur virgule et la version à argument unique de SetSize est appelé.

17voto

dasblinkenlight Points 264350

Cela s'explique par le fait que Le C++ ne traite pas la deuxième virgule comme un opérateur de virgule. :

La virgule dans diverses listes séparées par des virgules, telles que les listes d'arguments de fonctions. f(a, b, c) et des listes d'initialisation int a[] = {1,2,3} n'est pas l'opérateur virgule.

En ce qui concerne la première virgule, le C++ n'a d'autre choix que de la traiter comme un opérateur de virgule. Sinon, l'analyse serait invalide.

Une façon simple de voir les choses est de penser que dès que le parseur C++ trouve ? dans un contexte où les séparateurs de virgule sont autorisés, il recherche la correspondance entre : pour compléter la première partie de l'expression, puis correspond aussi peu que nécessaire pour compléter la deuxième expression. La deuxième virgule ne serait pas traitée comme un opérateur même si vous supprimez la surcharge à deux arguments.

9voto

Le compilateur vous avertit que vous jetez précisément 50% de vos littéraux à virgule flottante.

Décomposons-la.

// void SetSize(float w, float h, float d=0)
SetSize(true ? 12.f, 50.f : 50.f, 12.f);
//      ^^^^^^^^^^^^^^^^^^^^^^^^  ^^^^

Nous présentons ici une expression utilisant le opérateur conditionnel comme premier argument, et le littéral 12.f comme deuxième argument ; le troisième argument est laissé à sa valeur par défaut ( 0 ).

Oui, vraiment.

Il est analysé comme ceci (parce qu'il n'y a pas d'autre façon valide de l'analyser) :

SetSize( (true ? 12.f, 50.f : 50.f), 12.f);
//        ^^^^^^^^^^^^^^^^^^^^^^^^   ^^^^

La valeur du second argument est simple, examinons donc le premier :

true ? 12.f, 50.f : 50.f

Cela signifie :

  • Si c'est vrai, le résultat est 12.f, 50.f
  • Sinon, le résultat est 50.f

Eh bien, vrai est toujours vrai, donc nous pouvons immédiatement écarter la deuxième option.

Et l'expression 12.f, 50.f utilise le opérateur de virgule qui évalue les deux opérandes puis élimine le premier et donne le second, c'est-à-dire 50.f .

Par conséquent, l'ensemble est en fait :

SetSize(50.f, 12.f);

S'il ne s'agit pas d'une "énigme" de programmation obscure et inutile, c'est un morceau de code remarquablement stupide, avec un programmeur non instruit espérant "décomposer" l'expression en quelque chose de plus équivalent :

SetSize(
   (true ? 12.f : 50.f),
   (true ? 50.f : 12.f)
);

qui est toujours un code terrible et inutile, parce que vrai est toujours vrai.

(Évidemment, les valeurs sont différentes dans le cas où false est écrit à la place, mais la même logique s'applique).


Le vrai cas devrait être h=12, w=50 je pense...

C'est le cas. C'est ce que dit la sortie que tu as postée. C'est plus clair quand on ne réarrange pas arbitrairement les arguments, c'est-à-dire qu'ils sont w=50 h=12.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X