23 votes

Pourquoi les variables définies dans une conditionnelle ne peuvent-elles pas être construites avec des arguments ?

La question est simple. Pourquoi est-ce que ça compile :

bool b(true);
if (b) { /* */ }

Et ça compile :

if (bool b = true) { /* */ }

Mais pas ça :

if (bool b(true)) { /* */ }

Dans mon code réel, j'ai besoin de construire un objet et de le tester, tout en le faisant détruire à la fin du bloc if. En gros, je cherche quelque chose comme ça :

{
    Dingus dingus(another_dingus);
    if (dingus) {
        // ...
    }
}

Bien sûr, ça pourrait marcher :

if (Dingus dingus = another_dingus) { /* */ }

Mais alors je construis un Dingus et en appelant operator= sur elle. Il me semble logique que je puisse construire l'objet en utilisant le constructeur de mon choix.

Mais je ne comprends pas pourquoi ce n'est pas grammaticalement correct. J'ai testé avec G++ et MSVC++ et ils se plaignent tous les deux de cette construction, donc je suis sûr que cela fait partie de la spécification mais je suis curieux de savoir quel est le raisonnement pour cela et quelles sont les solutions de contournement non moches qu'il peut y avoir.

20voto

GManNickG Points 155079

C'est un peu technique. Il n'y a aucune raison pour que ce que vous voulez ne soit pas autorisé, mais ça ne l'est pas. C'est la grammaire.

Un site if est une déclaration de sélection, et elle prend la forme grammaticale :

if (condition) statement

Ici, condition peut être soit :

  • expression o
  • type-specifier-seq declarator = assignment-expression

Et voilà. Permettre une déclaration dans une condition est un cas spécial, et il doit suivre que ou votre programme est mal formé. Ils auraient probablement pu permettre l'initialisation directe au lieu de l'initialisation par copie, mais il n'y a pas vraiment de motivation pour le faire maintenant. Comme le souligne Johannes Schaub En revanche, ce changement casserait le code existant, ce qui signifie qu'il ne se produira jamais.

Let_Me_Be note que C++11 a ajouté une troisième forme (j'ignore ici les attributs) :

decl-specifier-seq declarator braced-init-list

Alors if (bool b{true}) est bien. (Cela ne peut pas casser un quelconque code existant valide).


Notez que votre question semble porter sur l'efficacité : ne vous inquiétez pas. Le compilateur élide la valeur temporaire et construit directement le côté gauche. Cependant, cela nécessite que votre type soit copiable (ou mobile en C++11).

5voto

Il faut noter que if(functor(f)(123)) ...; ne serait alors plus l'appel d'un foncteur anonyme avec l'argument 123 mais déclarerait un foncteur initialisé par 123.

Et je pense qu'introduire de tels pièges pour cette petite fonctionnalité n'en vaut pas la peine.


Comme la signification de ce qui précède n'est peut-être pas claire, examinons-la de plus près. Tout d'abord, rappelez-vous que les parenthèses autour d'un déclarateur sont autorisées, y compris dans le cas dégénéré où elles entourent directement un nom déclaré :

int(n) = 0; 
// same: int n = 0; 

int(n)(0);
// same: int n(0);

Les deux versions entre parenthèses sont ambiguës, car la première pourrait être une affectation et la seconde un appel de fonction. Mais les deux pourraient aussi être des déclarations. Et la norme dit qu'elles son les déclarations.

Si nous autorisons les initialisateurs de paren dans les conditions, nous introduisons cette dernière ambiguïté dans les conditions également, tout comme dans le cas des déclarations. Ainsi, nous transformerions les expressions de conditions valides qui sont utilisées aujourd'hui en déclarations après que la fonctionnalité soit supportée. Considérons

typedef bool(*handler_type)(int);

bool f(int) { /* ... */ }
bool f(int, int) { /* ... */ }

void call_it() {
   // user wants to call f(int), but it is overloaded!
   // -> user tries a cast...
   if(handler_type(f)(0)) {
     /* ... */
   }
}

Que pensez-vous qu'il va se passer ? Bien sûr, il n'entrera jamais dans le if car il déclare toujours un pointeur nul. Il n'appelle jamais la fonction f . Sans cette "fonctionnalité", il appellera correctement f parce que nous n'avons pas d'ambiguïté. Cela ne se limite pas à (f) mais aussi (*f) (déclare un pointeur), (&f) (déclare une référence) et al.

Encore une fois : Voulons-nous de tels écueils comme le prix pour une si petite fonctionnalité ? Je ne sais pas combien de personnes savent qu'elles peuvent déclarer des choses dans une condition.

3voto

Charles Bailey Points 244082

C'est une restriction grammaticale de la langue. La partie entre parenthèses dans un if peut être soit un expression ou il peut s'agir d'une forme restreinte de déclaration qui doit avoir l'une des formes :

spécification d'attribut-seq OPT decl-specifier-seq Déclarateur \= clause d'initialisation

spécification d'attribut-seq OPT decl-specifier-seq Déclarateur liste d'installation entre crochets

Aucune autre forme de déclaration n'est autorisée. Notez qu'il n'y a pas d'affectation ici, seulement une initialisation par copie.

Si vous voulez initialiser directement un objet dans une condition d'une instruction de sélection, vous devez utiliser la nouvelle forme d'une instruction de sélection. liste d'installation entre crochets (depuis C++11) :

if (Type var { init })
{
    // ...
}

1voto

Pubby Points 29386

Voici la solution à votre problème, même si elle ne répond pas tout à fait à la question :

if (bool b = bool(true)) { /* */ }

Il ne fait pas ce que vous pensez qu'il fait - bool(true) n'appelle pas le constructeur dans ce cas, il effectue un cast. Par exemple :

return foo(0);

est la même chose que :

return static_cast<foo>(0); // or (foo)0

Test :

struct foo {
  foo(int x) {
    std::cout << "ctor\n";
  }
  foo(const foo& x) {
    std::cout << "copy ctor\n";
  }
  operator bool() {
    return true;
  }

};

int main(int, char**) {
  if (foo x = foo(1)) { /* */ }
}

imprime "ctor". N'appelle pas le constructeur de la copie en raison de l'élision de la copie.

-3voto

John Points 1196

Car "=" est défini comme une fonction qui prend deux arguments et - après avoir effectué son travail - renvoie la valeur du premier. Le constructeur ne renvoie pas de valeur.

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