Parmi les nombreuses choses que Stack Overflow m'a apprises, il y a ce que l'on appelle «l'analyse la plus vexante», qui est classiquement illustrée avec une ligne telle que
A a(B()); //declares a function
Bien que cela, pour la plupart, semble intuitivement être la déclaration d'un objet a
de type A
, prenant un B
objet temporaire comme paramètre de constructeur, c'est en fait une déclaration d'une fonction a
retournant un A
, prenant un pointeur vers une fonction qui retourne B
et ne prend lui-même aucun paramètre . De même la ligne
A a(); //declares a function
relève également de la même catégorie, car au lieu d'un objet, il déclare une fonction. Maintenant, dans le premier cas, la solution de contournement habituelle pour ce problème consiste à ajouter un jeu supplémentaire de crochets / parenthèses autour du B()
, car le compilateur l'interprétera alors comme la déclaration d'un objet
A a((B())); //declares an object
Cependant, dans le second cas, faire de même conduit à une erreur de compilation
A a(()); //compile error
Ma question est: pourquoi? Oui, je suis très conscient que la bonne `` solution de contournement '' consiste à le changer en A a;
, mais je suis curieux de savoir ce que fait le supplément ()
pour le compilateur dans le premier exemple qui ne fonctionne pas lors de sa réapplication dans le deuxième exemple. La A a((B()));
solution de contournement est-elle une exception spécifique écrite dans la norme?
(B())
est juste une expression C ++, rien de plus. Ce n'est pas une sorte d'exception. La seule différence que cela fait est qu'il n'y a aucun moyen de l'analyser en tant que type, et donc ce n'est pas le cas.A a();
n'est pas de la même catégorie. Pour le compilateur , il n'y a jamais de manière différente de l'analyser: un initialiseur à cet endroit ne se compose jamais de parenthèses vides, il s'agit donc toujours d'une déclaration de fonction.A a();
n'est pas un exemple de l' analyse la plus vexante . C'est simplement une déclaration de fonction, comme c'est le cas en C.A a;
" est faux. Cela ne vous donnera pas l'initialisation d'un type POD. Pour obtenir une initialisation, écrivezA a{};
.Réponses:
Il n'y a pas de réponse éclairée, c'est juste parce qu'elle n'est pas définie comme syntaxe valide par le langage C ++ ... Il en est ainsi, par définition du langage.
Si vous avez une expression à l'intérieur, elle est valide. Par exemple:
Encore plus simple: parce que
(x)
c'est une expression C ++ valide, alors que ce()
n'est pas le cas.Pour en savoir plus sur la définition des langages et le fonctionnement des compilateurs, vous devez vous renseigner sur la théorie du langage formel ou plus particulièrement sur les grammaires sans contexte (CFG) et sur le matériel connexe comme les machines à états finis. Si cela vous intéresse bien que les pages wikipedia ne suffisent pas, vous devrez vous procurer un livre.
la source
(x)
c'est une expression C ++ valide, alors que ce()
n'est pas le cas.La solution finale à ce problème est de passer à la syntaxe d'initialisation uniforme C + 11 si vous le pouvez.
http://www.stroustrup.com/C++11FAQ.html#uniform-init
la source
Déclarateurs de fonction C
Tout d'abord, il y a C. En C,
A a()
c'est la déclaration de fonction. Par exemple,putchar
a la déclaration suivante. Normalement, ces déclarations sont stockées dans des fichiers d'en-tête, cependant rien ne vous empêche de les écrire manuellement, si vous savez à quoi ressemble la déclaration de fonction. Les noms d'argument sont facultatifs dans les déclarations, je l'ai donc omis dans cet exemple.Cela vous permet d'écrire le code comme celui-ci.
C vous permet également de définir des fonctions qui prennent des fonctions comme arguments, avec une belle syntaxe lisible qui ressemble à un appel de fonction (enfin, c'est lisible, tant que vous ne retournerez pas de pointeur sur la fonction).
Comme je l'ai mentionné, C permet d'omettre les noms d'argument dans les fichiers d'en-tête, donc
output_result
cela ressemblerait à ceci dans le fichier d'en-tête.Un argument dans le constructeur
Vous ne reconnaissez pas celui-là? Eh bien, laissez-moi vous le rappeler.
Oui, c'est exactement la même déclaration de fonction.
A
estint
,a
estoutput_result
etB
estint
.Vous pouvez facilement remarquer un conflit de C avec les nouvelles fonctionnalités de C ++. Pour être exact, les constructeurs étant le nom de classe et les parenthèses, et la syntaxe de déclaration alternative avec
()
au lieu de=
. De par sa conception, C ++ essaie d'être compatible avec le code C, et par conséquent, il doit gérer ce cas - même si pratiquement personne ne s'en soucie. Par conséquent, les anciennes fonctionnalités C ont priorité sur les nouvelles fonctionnalités C ++. La grammaire des déclarations essaie de faire correspondre le nom en tant que fonction, avant de revenir à la nouvelle syntaxe avec()
si elle échoue.Si l'une de ces fonctionnalités n'existait pas ou avait une syntaxe différente (comme
{}
dans C ++ 11), ce problème ne se serait jamais produit pour la syntaxe avec un argument.Maintenant, vous pouvez demander pourquoi
A a((B()))
fonctionne. Eh bien, déclaronsoutput_result
avec des parenthèses inutiles.Ça ne marchera pas. La grammaire exige que la variable ne soit pas entre parenthèses.
Cependant, C ++ attend ici une expression standard. En C ++, vous pouvez écrire le code suivant.
Et le code suivant.
C ++ s'attend à ce que l'expression entre parenthèses soit ... eh bien ... expression, contrairement au type que C attend. Les parenthèses ne signifient rien ici. Cependant, en insérant des parenthèses inutiles, la déclaration de fonction C n'est pas mise en correspondance et la nouvelle syntaxe peut être mise en correspondance correctement (qui attend simplement une expression, telle que
2 + 2
).Plus d'arguments dans le constructeur
Un argument est certainement intéressant, mais qu'en est-il de deux? Ce n'est pas que les constructeurs peuvent avoir un seul argument. L'une des classes intégrées qui prend deux arguments est
std::string
Tout va bien (techniquement, l'analyse serait la plus vexante si elle était écrite comme
std::string wat(int(), char())
, mais soyons honnêtes - qui écrirait cela? Mais supposons que ce code a un problème épineux. Vous supposeriez que vous devez mettre tout entre parenthèses.Pas tout à fait.
Je ne sais pas pourquoi g ++ essaie de se convertir
char
enconst char *
. Dans tous les cas, le constructeur a été appelé avec une seule valeur de typechar
. Il n'y a pas de surcharge qui a un argument de typechar
, donc le compilateur est confus. Vous vous demandez peut-être pourquoi l'argument est de type char?Oui,
,
voici un opérateur virgule. L'opérateur virgule prend deux arguments et donne l'argument de droite. Ce n'est pas vraiment utile, mais c'est quelque chose que je connais pour mon explication.Au lieu de cela, pour résoudre l'analyse la plus vexante, le code suivant est nécessaire.
Les arguments sont entre parenthèses, pas l'expression entière. En fait, une seule des expressions doit être entre parenthèses, car il suffit de rompre légèrement avec la grammaire C pour utiliser la fonctionnalité C ++. Les choses nous amènent au point de zéro argument.
Zéro argument dans le constructeur
Vous avez peut-être remarqué la
eighty_four
fonction dans mon explication.Oui, cela est également affecté par l'analyse la plus vexante. C'est une définition valide, et celle que vous avez probablement vue si vous avez créé des fichiers d'en-tête (et vous devriez). L'ajout de parenthèses ne résout pas le problème.
Pourquoi est-ce si? Eh bien, ce
()
n'est pas une expression. En C ++, vous devez mettre une expression entre parenthèses. Vous ne pouvez pas écrireauto value = ()
en C ++, car()
cela ne veut rien dire (et même si c'était le cas, comme un tuple vide (voir Python), ce serait un argument, pas zéro). En pratique, cela signifie que vous ne pouvez pas utiliser la syntaxe abrégée sans utiliser la syntaxe de C ++ 11{}
, car il n'y a pas d'expressions à mettre entre parenthèses et la grammaire C pour les déclarations de fonction s'appliquera toujours.la source
Vous pourriez à la place
utilisation
la source
int a = int();
initialisea
avec 0,int a;
laissea
non initialisé. Une solution de contournement correcte consiste à utiliserA a = {};
pour les agrégats,A a;
lorsque l'initialisation par défaut fait ce que vous voulez, etA a = A();
dans tous les autres cas, ou simplement à l'utiliser de manièreA a = A();
cohérente. En C ++ 11, utilisez simplementA a {};
Les parens les plus intimes de votre exemple seraient une expression, et en C ++ la grammaire définit un
expression
comme étant unassignment-expression
ou un autreexpression
suivi d'une virgule et d'un autreassignment-expression
(Annexe A.4 - Récapitulatif de grammaire / Expressions).La grammaire définit en outre un
assignment-expression
comme l'un des nombreux autres types d'expression, dont aucun ne peut être rien (ou seulement des espaces).Donc, la raison pour laquelle vous ne pouvez pas avoir
A a(())
est simplement parce que la grammaire ne le permet pas. Cependant, je ne peux pas répondre pourquoi les personnes qui ont créé C ++ n'ont pas permis cette utilisation particulière de parens vides comme une sorte de cas spécial - je suppose qu'ils préfèrent ne pas mettre dans un cas aussi spécial s'il y avait une alternative raisonnable.la source