TL; DR
Les parenthèses supplémentaires changent la signification d'un programme C ++ dans les contextes suivants:
- empêcher la recherche de noms dépendants des arguments
- activation de l'opérateur virgule dans les contextes de liste
- résolution d'ambiguïté des analyses vexantes
- déduire la référence dans les
decltype
expressions
- prévention des erreurs de macro de préprocesseur
Empêcher la recherche de noms dépendants des arguments
Comme il est détaillé dans l'annexe A de la norme, a post-fix expression
du formulaire (expression)
est un primary expression
, mais pas un id-expression
, et donc pas un unqualified-id
. Cela signifie que la recherche de nom dépendante de l'argument est empêchée dans les appels de fonction de la forme (fun)(arg)
par rapport à la forme conventionnelle fun(arg)
.
3.4.2 Recherche de nom dépendante de l'argument [basic.lookup.argdep]
1 Lorsque l'expression de suffixe dans un appel de fonction (5.2.2) est un identifiant non qualifié , d'autres espaces de noms non pris en compte lors de la recherche non qualifiée habituelle (3.4.1) peuvent être recherchés, et dans ces espaces de noms, une fonction amie de portée d'espace de noms ou des déclarations de modèle de fonction (11.3) non visibles autrement peuvent être trouvées. Ces modifications de la recherche dépendent des types d'arguments (et pour les arguments de modèle de modèle, de l'espace de nom de l'argument de modèle). [ Exemple:
namespace N {
struct S { };
void f(S);
}
void g() {
N::S s;
f(s); // OK: calls N::f
(f)(s); // error: N::f not considered; parentheses
// prevent argument-dependent lookup
}
—End exemple]
Activation de l'opérateur virgule dans les contextes de liste
L'opérateur virgule a une signification particulière dans la plupart des contextes de type liste (arguments de fonction et de modèle, listes d'initialiseurs, etc.). Les parenthèses du formulaire a, (b, c), d
dans de tels contextes peuvent activer l'opérateur virgule par rapport au formulaire normal a, b, c, d
où l'opérateur virgule ne s'applique pas.
5.18 Opérateur virgule [expr.comma]
2 Dans les contextes où la virgule a une signification particulière, [Exemple: dans les listes d'arguments de fonctions (5.2.2) et les listes d'initialiseurs (8.5) - exemple de fin] l'opérateur virgule tel que décrit dans l'Article 5 ne peut apparaître qu'entre parenthèses. [ Exemple:
f(a, (t=3, t+2), c);
a trois arguments, dont le second a la valeur 5. —fin exemple]
Résolution de l'ambiguïté des analyses vexantes
La rétrocompatibilité avec C et sa syntaxe de déclaration de fonction arcane peut conduire à des ambiguïtés d'analyse surprenantes, appelées analyses vexantes. Essentiellement, tout ce qui peut être analysé en tant que déclaration sera analysé comme une seule , même si une analyse concurrente s'appliquerait également.
6.8 Résolution d'ambiguïté [stmt.ambig]
1 Il y a une ambiguïté dans la grammaire impliquant des instructions d'expression et des déclarations : Une instruction d'expression avec une conversion de type explicite de style fonction (5.2.3) comme sous-expression la plus à gauche peut être indiscernable d'une déclaration où le premier déclarateur commence par un ( . Dans ces cas , la déclaration est une déclaration .
8.2 Résolution des ambiguïtés [dcl.ambig.res]
1 L'ambiguïté résultant de la similitude entre un cast de style fonction et une déclaration mentionnée au 6.8 peut également se produire dans le contexte d'une déclaration . Dans ce contexte, le choix est entre une déclaration de fonction avec un ensemble redondant de parenthèses autour d'un nom de paramètre et une déclaration d'objet avec un cast de style fonction comme initialiseur. Tout comme pour les ambiguïtés mentionnées en 6.8, la résolution est de considérer toute construction qui pourrait éventuellement être une déclaration comme une déclaration . [Remarque: Une déclaration peut être explicitement désambiguïsée par une conversion de style non fonction, par un = pour indiquer l'initialisation ou en supprimant les parenthèses redondantes autour du nom du paramètre. —End note] [Exemple:
struct S {
S(int);
};
void foo(double a) {
S w(int(a)); // function declaration
S x(int()); // function declaration
S y((int)a); // object declaration
S z = int(a); // object declaration
}
—End exemple]
Un exemple célèbre de ceci est le Most Vexing Parse , un nom popularisé par Scott Meyers dans le point 6 de son livre Effective STL :
ifstream dataFile("ints.dat");
list<int> data(istream_iterator<int>(dataFile), // warning! this doesn't do
istream_iterator<int>()); // what you think it does
Cela déclare une fonction data
, dont le type de retour est list<int>
. Les données de fonction prennent deux paramètres:
- Le premier paramètre est nommé
dataFile
. C'est le type istream_iterator<int>
. Les parenthèses autour dataFile
sont superflues et ignorées.
- Le deuxième paramètre n'a pas de nom. Son type est un pointeur vers une fonction ne prenant rien et renvoyant un fichier
istream_iterator<int>
.
Placer des parenthèses supplémentaires autour du premier argument de fonction (les parenthèses autour du deuxième argument sont illégales) résoudra l'ambiguïté
list<int> data((istream_iterator<int>(dataFile)), // note new parens
istream_iterator<int>()); // around first argument
// to list's constructor
C ++ 11 a une syntaxe d'initialisation d'accolades qui permet de contourner ces problèmes d'analyse dans de nombreux contextes.
Déduire le référencé dans les decltype
expressions
Contrairement à la auto
déduction de type, decltype
permet de déduire la référence (références lvalue et rvalue). Les règles distinguent les expressions decltype(e)
et decltype((e))
:
7.1.6.2 Spécificateurs de type simple [dcl.type.simple]
4 Pour une expression e
, le type désigné pardecltype(e)
est défini comme suit:
- si e
est une expression id sans parenthèse ou un accès de membre de classe non parenthèse (5.2.5), decltype(e)
est le type de l'entité nommée par e
. S'il n'y a pas une telle entité, ou si e
nomme un ensemble de fonctions surchargées, le programme est mal formé;
- sinon, si e
est une valeur x, decltype(e)
est T&&
, où T
est le type de e
;
- sinon, si e
est une valeur l, decltype(e)
est T&
, où T
est le type de e
;
- sinon, decltype(e)
est le type de e
.
L'opérande du spécificateur decltype est un opérande non évalué (article 5). [ Exemple:
const int&& foo();
int i;
struct A { double x; };
const A* a = new A();
decltype(foo()) x1 = 0; // type is const int&&
decltype(i) x2; // type is int
decltype(a->x) x3; // type is double
decltype((a->x)) x4 = x3; // type is const double&
—Fin exemple] [Remarque: les règles de détermination des types impliquant
decltype(auto)
sont spécifiées au 7.1.6.4. —End note]
Les règles pour decltype(auto)
ont une signification similaire pour les parenthèses supplémentaires dans le RHS de l'expression d'initialisation. Voici un exemple de la FAQ C ++ et de ces questions et réponses connexes
decltype(auto) look_up_a_string_1() { auto str = lookup1(); return str; } //A
decltype(auto) look_up_a_string_2() { auto str = lookup1(); return(str); } //B
Le premier retourne string
, le second retourne string &
, qui est une référence à la variable locale str
.
Prévention des erreurs liées aux macros de préprocesseur
Il existe une foule de subtilités avec les macros de préprocesseur dans leur interaction avec le langage C ++ proprement dit, dont les plus courantes sont répertoriées ci-dessous
- en utilisant des parenthèses autour des paramètres de macro à l'intérieur de la définition de macro
#define TIMES(A, B) (A) * (B);
afin d'éviter la préséance indésirable des opérateurs (par exemple, dans TIMES(1 + 2, 2 + 1)
lequel donne 9 mais donnerait 6 sans les parenthèses autour (A)
et(B)
- en utilisant des parenthèses autour des arguments de macro comportant des virgules à l'intérieur:
assert((std::is_same<int, int>::value));
qui autrement ne compileraient pas
- en utilisant des parenthèses autour d'une fonction pour protéger contre l'expansion des macros dans les en-têtes inclus:
(min)(a, b)
(avec l'effet secondaire indésirable de désactiver également ADL)
&(C::f)
, l'opérande de&
est toujoursC::f
, n'est-ce pas?