Analyse la plus vexante: pourquoi ne pas a (()); travail?

Parmi les nombreuses choses que Stack Overflow m’a enseignées, il y a ce que l’on appelle la «parade la plus vexante», qui est classiquement démontrée avec une ligne telle que

A a(B()); //declares a function 

Bien que cela semble intuitivement, pour la plupart, être la déclaration d’un object a de type A , prenant un object B temporaire comme paramètre constructeur, il s’agit en réalité d’une déclaration de fonction renvoyant un A , en prenant un pointeur sur une fonction qui renvoie B et lui-même ne prend aucun paramètre. De même la ligne

 A a(); //declares a function 

tombe également dans la même catégorie, car au lieu d’un object, il déclare une fonction. Maintenant, dans le premier cas, la solution de contournement habituelle pour ce problème consiste à append un ensemble supplémentaire de parenthèses / parenthèses autour du B() , car le compilateur l’interprétera alors comme la déclaration d’un object.

 A a((B())); //declares an object 

Cependant, dans le second cas, faire la même chose entraîne une erreur de compilation

 A a(()); //comstack error 

Ma question est: pourquoi? Oui, je sais très bien que la «solution de contournement» correcte est de la changer en A a; , mais je suis curieux de savoir ce que fait extra () pour le compilateur dans le premier exemple qui ne fonctionne alors pas lors de sa réapplication dans le second exemple. Est le A a((B())); solution de contournement une exception spécifique écrite dans la norme?

Il n’y a pas de réponse éclairée, c’est juste parce que ce n’est pas défini comme une syntaxe valide par le langage C ++ … Il en est ainsi, par définition du langage.

Si vous avez une expression à l’intérieur, alors c’est valide. Par exemple:

  ((0));//comstacks 

Encore plus simple: car (x) est une expression C ++ valide, alors que () ne l’est pas.

Pour en savoir plus sur la façon dont les langages sont définis et sur le fonctionnement des compilateurs, vous devez en apprendre davantage sur la théorie des langages formels ou, plus spécifiquement, sur les grammaires sans contexte (CFG) et les matériels connexes tels que les automates à états finis. Si cela vous intéresse, bien que les pages Wikipédia ne soient pas suffisantes, vous devrez vous procurer un livre.

La solution finale à ce problème consiste à passer à la syntaxe d’initialisation uniforme C + 11 si vous le pouvez.

 A a{}; 

http://www.stroustrup.com/C++11FAQ.html#uniform-init

Déclarateurs de fonctions C

Tout d’abord, il y a C. Dans C, A a() 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, mais rien ne vous empêche de les écrire manuellement, si vous savez à quoi ressemble la déclaration de fonction. Les noms des arguments sont facultatifs dans les déclarations, donc je l’ai omis dans cet exemple.

 int putchar(int); 

Cela vous permet d’écrire le code comme ceci.

 int puts(const char *); int main() { puts("Hello, world!"); } 

C vous permet également de définir des fonctions qui prennent des fonctions comme arguments, avec une syntaxe lisible qui ressemble à un appel de fonction (enfin, c’est lisible, tant que vous ne renvoyez pas un pointeur sur la fonction).

 #include  int eighty_four() { return 84; } int output_result(int callback()) { printf("Returned: %d\n", callback()); return 0; } int main() { return output_result(eighty_four); } 

Comme je l’ai mentionné, C permet d’omettre les noms d’argument dans les fichiers d’en-tête. Par conséquent, output_result devrait ressembler à ceci dans le fichier d’en-tête.

 int output_result(int()); 

Un argument en constructeur

Tu ne reconnais pas celui-là? Eh bien, laissez-moi vous rappeler.

 A a(B()); 

Oui, c’est exactement la même déclaration de fonction. A est int , a est output_result et B est int .

Vous pouvez facilement remarquer un conflit de C avec les nouvelles fonctionnalités de C ++. Pour être exact, les constructeurs sont des noms de classe et des parenthèses, et une syntaxe de déclaration alternative avec () au lieu de = . De par sa conception, C ++ essaie d’être compatible avec le code C, et doit donc faire face à 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 tente 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 serait jamais arrivé pour la syntaxe avec un seul argument.

Maintenant, vous pouvez demander pourquoi A a((B())) fonctionne. Eh bien, déclarons output_result avec des parenthèses inutiles.

 int output_result((int())); 

Ça ne marchera pas La grammaire exige que la variable ne soit pas entre parenthèses.

 :1:19: error: expected declaration specifiers or '...' before '(' token 

Cependant, C ++ attend une expression standard ici. En C ++, vous pouvez écrire le code suivant.

 int value = int(); 

Et le code suivant.

 int value = ((((int())))); 

C ++ s’attend à ce que l’expression entre parenthèses soit … bien … l’expression, contrairement au type attendu par C. Les parenthèses ne veulent rien dire ici. Cependant, en insérant des parenthèses inutiles, la déclaration de la fonction C ne correspond pas et la nouvelle syntaxe peut correspondre correctement (qui attend simplement une expression, telle que 2 + 2 ).

Plus d’arguments dans constructeur

Un argument est certainement bon, mais qu’en est-il de deux? Ce n’est pas que les constructeurs ne peuvent avoir qu’un seul argument. Une des classes intégrées qui prend deux arguments est std::ssortingng

 std::ssortingng hundred_dots(100, '.'); 

C’est très bien (techniquement, ce serait plus malin d’écrire si c’était écrit std::ssortingng wat(int(), char()) , mais soyons honnêtes – qui écrirait ça? code a un problème épineux.Vous supposez que vous devez tout mettre entre parenthèses.

 std::ssortingng hundred_dots((100, '.')); 

Pas tout à fait.

 :2:36: error: invalid conversion from 'char' to 'const char*' [-fpermissive] In file included from /usr/include/c++/4.8/ssortingng:53:0, from :1: /usr/include/c++/4.8/bits/basic_ssortingng.tcc:212:5: error: initializing argument 1 of 'std::basic_ssortingng<_CharT, _Traits, _Alloc>::basic_ssortingng(const _CharT*, const _Alloc&) [with _CharT = char; _Traits = std::char_traits; _Alloc = std::allocator]' [-fpermissive] basic_ssortingng<_CharT, _Traits, _Alloc>:: ^ 

Je ne sais pas pourquoi g ++ essaie de convertir char en const char * . De toute façon, le constructeur a été appelé avec une seule valeur de type char . Il n’y a pas de surcharge qui a un argument de type char , donc le compilateur est confus. Vous pouvez demander – pourquoi l’argument est de type char?

 (100, '.') 

Oui, voici un opérateur de 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 à connaître pour mon explication.

Au lieu de cela, pour résoudre l’parsing la plus vexante, le code suivant est nécessaire.

 std::ssortingng hundred_dots((100), ('.')); 

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 la grammaire C pour utiliser la fonctionnalité C ++. Les choses nous amènent au sharepoint zéro argument.

Zéro argument dans constructeur

Vous avez peut-être remarqué la fonction eighty_foureighty_foureighty_four dans mon explication.

 int eighty_four(); 

Oui, ceci est également affecté par l’parsing la plus délicate. C’est une définition valide, que vous avez probablement vue si vous avez créé des fichiers d’en-tête (et vous devriez le faire). L’ajout de parenthèses ne le répare pas.

 int eighty_four(()); 

Pourquoi est-ce si? Eh bien, () n’est pas une expression. En C ++, vous devez mettre une expression entre parenthèses. Vous ne pouvez pas écrire auto value = () en C ++, car () ne signifie rien (et même si c’était le cas, comme un tuple vide (voir Python), ce serait un argument, pas un zéro). En pratique, cela signifie que vous ne pouvez pas utiliser la syntaxe abrégée sans utiliser la syntaxe {} C ++ 11, car il n’y a pas d’expressions à mettre entre parenthèses, et la grammaire C pour les déclarations de fonctions s’appliquera toujours.

Vous pourriez au lieu de cela

 A a(()); 

utilisation

 A a=A(); 

Les parens les plus internes de votre exemple seraient une expression, et en C ++, la grammaire définit une expression comme étant une expression d’ assignment-expression ou une autre expression suivie d’une virgule et d’une autre assignment-expression (Annexe A.4 – Résumé de grammaire / Expressions).

La grammaire définit en outre une 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 à la question de savoir pourquoi les personnes qui ont créé C ++ n’autorisaient pas cette utilisation particulière des parens vides comme une sorte de cas particulier. une alternative raisonnable.