Je peux le faire:
#include <iostream>
int counter;
int main()
{
struct Boo
{
Boo(int num)
{
++counter;
if (Rand() % num < 7) Boo(8);
}
};
Boo(8);
return 0;
}
Cela compilera bien, le résultat de mon compteur est 21. Cependant, lorsque j'essaie de créer l'objet Boo
en passant l'argument du constructeur au lieu d'un littéral entier, j'obtiens une erreur de compilation:
#include <iostream>
int counter;
int main()
{
struct Boo
{
Boo(int num)
{
++counter;
if (Rand() % num < 7) Boo(num); // No default constructor
// exists for Boo
}
};
Boo(8);
return 0;
}
Comment un constructeur par défaut est-il appelé dans le deuxième exemple mais pas dans le premier? C'est une erreur que j'ai sur Visual Studio 2017.
Sur le compilateur C++ en ligne, onlineGDB, j'obtiens les erreurs:
error: no matching function for call to ‘main()::Boo::Boo()’
if (Rand() % num < 7) Boo(num);
^
note: candidate expects 1 argument, 0 provided
Clang donne ce message d'avertissement:
<source>:12:16: warning: parentheses were disambiguated as redundant parentheses around declaration of variable named 'num' [-Wvexing-parse]
Boo(num); // No default constructor
^~~~~
C'est une question d'analyse très épineuse. Parce que Boo
est le nom d'un type de classe et que num
n'est pas un nom de type, Boo(num);
pourrait être la construction d'un temporaire de type Boo
avec num
étant un argument du constructeur de Boo
ou il pourrait s'agir d'une déclaration Boo num;
avec des parenthèses supplémentaires autour du déclarateur num
(que les déclarants peuvent toujours avoir). Si les deux sont des interprétations valides, la norme exige que le compilateur assume une déclaration.
S'il est analysé en tant que déclaration, alors Boo num;
appellerait le constructeur par défaut (le constructeur sans arguments), qui n'est déclaré ni implicitement ni par vous (car vous avez déclaré un autre constructeur). Par conséquent, le programme est mal formé.
Ce n'est pas un problème avec Boo(8);
, parce que 8
ne peut pas être l'identifiant d'une variable (déclarator-id), il est donc analysé comme un appel créant une Boo
temporaire avec 8
comme argument au constructeur, n'appelant ainsi pas le constructeur par défaut (qui n'est pas déclaré), mais celui que vous avez défini manuellement.
Vous pouvez distinguer cette déclaration d'une déclaration en utilisant Boo{num};
au lieu de Boo(num);
(car {}
autour du déclarateur n'est pas autorisé), en faisant de la variable temporaire une variable nommée, par exemple. Boo temp(num);
, ou en le mettant comme opérande dans une autre expression, par ex. (Boo(num));
, (void)Boo(num);
, etc.
Notez que la déclaration serait bien formée si le constructeur par défaut était utilisable, car il se trouve dans la portée du bloc de branche de if
plutôt que dans celle de la fonction et occulterait simplement le num
dans la liste des paramètres de la fonction. .
Dans tous les cas, il ne semble pas judicieux d’abuser de la création temporaire d’objets pour quelque chose qui devrait être un appel de fonction normal (membre).
Ce type particulier d’analyse la plus frustrante avec un nom non typé unique entre parenthèses ne peut se produire que parce que l’intention est de créer un temporaire et de l’écarter immédiatement, ou bien si l’intention est de créer un temporaire utilisé directement comme initialiseur, par exemple. Boo boo(Boo(num));
(déclare en fait la fonction boo
en prenant un paramètre nommé num
avec le type Boo
et en retournant Boo
).
Il n'est généralement pas prévu de supprimer immédiatement les éléments temporaires et d'éviter l'initialisation avec des doubles parenthèses (Boo boo{Boo(num)}
, Boo boo(Boo{num})
ou Boo boo((Boo(num)));
, mais pas Boo boo(Boo((num)));
).
Si Boo
n'était pas un nom de type, il ne pourrait pas s'agir d'une déclaration et aucun problème ne se produirait.
Je tiens également à souligner que Boo(8);
crée un nouveau temporaire de type Boo
, même à l'intérieur de la portée de la classe et de la définition du constructeur. Comme on pourrait le penser à tort, ce n'est pas un appel au constructeur avec le pointeur this
de l'appelant, comme pour les fonctions de membre non statiques habituelles. Il n'est pas possible d'appeler un autre constructeur de cette manière à l'intérieur du corps du constructeur. Cela n'est possible que dans la liste d'initialisation des membres du constructeur.
Cela se produit même si la déclaration est mal formée à cause d'un constructeur manquant, à cause de [stmt.ambig]/ :
La désambiguïsation est purement syntaxique; c'est-à-dire que la signification des noms apparaissant dans une telle déclaration, qu'ils soient ou non des noms de type, n'est généralement pas utilisée ou modifiée par la désambiguïsation.
[...]
La désambiguïsation précède l'analyse syntaxique, et une déclaration désambiguïsée en tant que déclaration peut être une déclaration mal formée.
Corrigé en mode édition: j'ai oublié que la déclaration en question se trouvait dans une autre portée que le paramètre de fonction et que la déclaration était donc bien formée si le constructeur était disponible. Ceci n'est en aucun cas pris en compte lors de l'homonymie. Également développé sur certains détails.
Ceci est connu sous le nom parse le plus frustrant (Le terme a été utilisé par Scott Meyers dans Effective STL) .
Boo(num)
n'appelle pas le constructeur et ne crée pas de temporaire. Clang donne un bon avertissement à voir (même avec le bon nom W vexing-parse ):
<source>:12:38: warning: parentheses were disambiguated as redundant parentheses around declaration of variable named 'num' [-Wvexing-parse]
Donc, ce que voit le compilateur est équivalent à
Boo num;
qui est une décélération variable. Vous avez déclaré une variable Boo avec le nom num, qui nécessite le constructeur par défaut, même si vous vouliez créer un objet Boo temporaire. La norme c ++ exige que le compilateur dans votre cas suppose qu'il s'agit d'une déclaration de variable. Vous pouvez maintenant dire: "Hé, num est un int, ne fais pas ça." Cependant, le standard dit :
La désambiguïsation est purement syntaxique; c'est-à-dire que la signification des noms apparaissant dans une telle déclaration, qu'ils soient ou non des noms de type, n'est généralement pas utilisée ou modifiée par la désambiguïsation. Les modèles de classe sont instanciés si nécessaire pour déterminer si un nom qualifié est un nom de type. La désambiguïsation précède l'analyse syntaxique, et une déclaration désambiguïsée en tant que déclaration peut être une déclaration mal formée. Si, lors de l'analyse, un nom dans un paramètre de modèle est lié différemment de celui qu'il aurait été lors d'une analyse d'essai, le programme est mal formé. Aucun diagnostic n'est requis. [Remarque: Cela ne peut se produire que lorsque le nom est déclaré plus tôt dans la déclaration. - note de fin]
Donc, il n'y a pas moyen de sortir de cela.
Cela ne peut pas arriver pour Boo(8)
, car l'analyseur peut être sûr que ce n'est pas une décleration (8 n'est pas un nom d'identifiant valide) et invoque le constructeur Boo(int)
.
Au fait: Vous pouvez préciser l’ambiguïté en utilisant des parenthèses fermantes:
if (Rand() % num < 7) (Boo(num));
ou à mon avis mieux, utilisez la nouvelle syntaxe d'initialisation uniforme
if (Rand() % num < 7) Boo{num};
Voici un avertissement
truct_init.cpp: 11: 11: erreur: redéfinition de 'num' avec un type différent: 'Boo' vs 'int'