web-dev-qa-db-fra.com

C++, déclaration de variable dans l'expression 'if'

Que se passe t-il ici?

if(int a = Func1())
{
    // Works.
}

if((int a = Func1()))
{
    // Fails to compile.
}

if((int a = Func1())
    && (int b = Func2()))
)
{
    // Do stuff with a and b.
    // This is what I'd really like to be able to do.
}

La section 6.4.3 de la norme 2003 explique comment la portée des variables déclarées dans une condition d'instruction de sélection s'étend jusqu'à la fin des sous-états contrôlés par la condition. Mais je ne vois pas où il est dit que vous ne pouvez pas mettre de parenthèse autour de la déclaration, pas plus que dans une déclaration par condition.

Cette limitation est agaçante même dans les cas où une seule déclaration dans la condition est requise. Considère ceci.

bool a = false, b = true;

if(bool x = a || b)
{

}

Si je veux saisir la portée "if" -body avec x défini sur false, la déclaration nécessite une parenthèse (l'opérateur de l'affectation ayant une priorité inférieure à celle du OU logique), mais comme la parenthèse ne peut pas être utilisée, elle nécessite une déclaration de x en dehors le corps, laissant filtrer cette déclaration dans une plus grande mesure que ce qui est souhaité. Cet exemple est évidemment trivial, mais un cas plus réaliste serait celui où a et b sont des fonctions renvoyant des valeurs qui doivent être testées. 

Est-ce que ce que je veux faire est non conforme à la norme ou mon compilateur ne fait-il que briser mes couilles (VS2008)?

96
Neutrino

La condition dans une instruction if ou while peut être une expression , ou une seule variable déclaration (avec initialisation).

Vos deuxième et troisième exemples ne sont ni des expressions valides, ni des déclarations valides, car une déclaration ne peut pas faire partie d'une expression. Bien qu'il soit utile de pouvoir écrire du code comme dans votre troisième exemple, cela nécessiterait un changement important de la syntaxe du langage.

Je ne vois pas où il est dit qu'il est impossible de mettre une parenthèse autour de la déclaration, pas plus que dans une déclaration par condition.

La syntaxe spécifiée dans 6.4/1 donne les informations suivantes pour la condition:

condition:
    expression
    type-specifier-seq declarator = assignment-expression

spécifier une seule déclaration, sans parenthèses ni autres ornements.

88
Mike Seymour

Je pense que vous avez déjà fait allusion à la question. Que doit faire le compilateur avec ce code?

if (!((1 == 0) && (bool a = false))) {
    // what is "a" initialized to?

L'opérateur "&&" est un ET logique de court-circuit. Cela signifie que si la première partie (1==0) s'avère être fausse, la seconde partie (bool a = false) ne doit pas être évaluée car il est déjà connu que la réponse finale sera fausse. Si (bool a = false) n'est pas évalué, que faire ensuite avec du code qui utilise a? Ne ferions-nous pas simplement initialiser la variable et la laisser indéfinie? Serions-nous initialiser à la valeur par défaut? Et si le type de données était une classe et que cela avait des effets secondaires indésirables? Et si, au lieu de bool, vous utilisiez une classe et qu’elle n’avait pas de constructeur par défaut, de sorte que l’utilisateur doit fournir les paramètres - que faisons-nous alors?

Voici un autre exemple:

class Test {
public:
    // note that no default constructor is provided and user MUST
    // provide some value for parameter "p"
    Test(int p);
}

if (!((1 == 0) && (Test a = Test(5)))) {
    // now what do we do?!  what is "a" set to?

Il semble que la limitation que vous avez trouvée semble parfaitement raisonnable: elle empêche ce type d'ambiguïté de se produire.

97
James Johnston

A partir de C++ 17, ce que vous essayiez de faire est enfin possible :

if (int a = Func1(), b = Func2(); a && b)
{
    // Do stuff with a and b.
}

Notez l'utilisation de ; au lieu de , pour séparer la déclaration et la condition réelle.

32
fwyzard

Si vous souhaitez inclure des variables dans une portée plus étroite, vous pouvez toujours utiliser un { } supplémentaire.

//just use { and }
{
    bool a = false, b = true;

    if(bool x = a || b)
    {
        //...
    }
}//a and b are out of scope
19
crashmstr

La dernière section fonctionne déjà, il vous suffit de l'écrire légèrement différent:

if (int a = Func1())
{
   if (int b = Func2())
   {
        // do stuff with a and b
   }
}
18
Bo Persson

Voici une solution de contournement moche utilisant une boucle (si les deux variables sont des entiers):

#include <iostream>

int func1()
{
    return 4;
}

int func2()
{
    return 23;
}

int main()
{
    for (int a = func1(), b = func2(), i = 0;
        i == 0 && a && b; i++)
    {
        std::cout << "a = " << a << std::endl;
        std::cout << "b = " << b << std::endl;
    }

    return 0;
}

Mais cela va dérouter les autres programmeurs et c'est un code plutôt mauvais, donc non recommandé.

Un simple bloc {} (comme déjà recommandé) est beaucoup plus facile à lire:

{
    int a = func1();
    int b = func2();

    if (a && b)
    {
        std::cout << "a = " << a << std::endl;
        std::cout << "b = " << b << std::endl;
    }
}
2
basic6

Une chose à noter, c'est aussi que les expressions à l'intérieur du plus grand if-block

if (!((1 == 0) && (bool a = false)))

ne sont pas nécessairement garantis d'être évalués de gauche à droite. Un bogue plutôt subtil que j’avais au début de la journée avait trait au fait que le compilateur testait en réalité de droite à gauche au lieu de gauche à droite.

1
DukeBrymin

Avec un peu de magie de modèle, vous pouvez en quelque sorte contourner le problème de l'impossibilité de déclarer plusieurs variables:

#include <stdio.h>

template <class LHS, class RHS>
struct And_t {
  LHS lhs;
  RHS rhs;

  operator bool () {
    bool b_lhs(lhs);
    bool b_rhs(rhs);
    return b_lhs && b_rhs;
  }
};
template <class LHS, class RHS> 
And_t<LHS, RHS> And(const LHS& lhs, const RHS& rhs) { return {lhs, rhs}; }

template <class LHS, class RHS>
struct Or_t {
LHS lhs;
RHS rhs;

  operator bool () {
    bool b_lhs(lhs);
    bool b_rhs(rhs);
    return b_lhs || b_rhs;
  }
};
template <class LHS, class RHS> 
Or_t<LHS, RHS> Or(const LHS& lhs, const RHS& rhs) { return {lhs, rhs}; }

int main() {
  if (auto i = And(1, Or(0, 3))) {
    printf("%d %d %d\n", i.lhs, i.rhs.lhs, i.rhs.rhs);
  }
  return 0;
}

(Notez que ceci perd l'évaluation du court-circuit.) 

0
BCS