Les parenthèses en C++ sont utilisées à de nombreux endroits: par ex. dans les appels de fonction et les expressions de regroupement pour remplacer la priorité de l'opérateur. Mis à part les parenthèses supplémentaires illégales (telles que les listes d'arguments d'appel de fonction), une règle générale - mais non absolue - de C++ est que les parenthèses supplémentaires ne font jamais de mal :
5.1 Expressions primaires [expr.prim]
5.1.1 Général [expr.prim.general]
6 Une expression entre parenthèses est une expression principale dont le type et la valeur sont identiques à ceux de l'expression incluse. La présence de parenthèses n'affecte pas si l'expression est une valeur l. L'expression entre parenthèses peut être utilisée exactement dans les mêmes contextes que ceux où l'expression incluse peut être utilisée, et avec la même signification, sauf indication contraire .
Question : dans quels contextes les parenthèses supplémentaires modifient-elles la signification d'un programme C++, à part remplacer la priorité de base de l'opérateur?
[~ # ~] note [~ # ~] : je considère la restriction de pointeur vers membre syntaxe à &qualified-id
sans parenthèses pour être hors de portée car il restreint la syntaxe plutôt que d'autoriser deux syntaxes avec des significations différentes. De même, l'utilisation de parenthèses dans les définitions de macro de préprocesseur protège également contre la priorité des opérateurs indésirables.
Des parenthèses supplémentaires modifient la signification d'un programme C++ dans les contextes suivants:
decltype
Comme détaillé à l'annexe A de la norme, un 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 postfixe dans un appel de fonction (5.2.2) est un identifiant non qualifié , les autres espaces de noms ne sont pas pris en compte lors de la recherche habituelle non qualifiée (3.4 .1) peut être recherchée, et dans ces espaces de noms, des fonctions ami 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, l'espace de noms 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
}
—Fin exemple]
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'initialisation, etc.). Les parenthèses de la forme a, (b, c), d
dans de tels contextes peuvent activer l'opérateur virgule par rapport à la forme régulière 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 spéciale, [Exemple: dans les listes d'arguments des fonctions (5.2.2) et les listes d'initialiseurs (8.5) —Fin exemple], l'opérateur virgule décrit à 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]
La compatibilité descendante avec C et sa syntaxe de déclaration de fonction arcanique peut conduire à des ambiguïtés d'analyse surprenantes, appelées analyses vexantes. Essentiellement, tout ce qui peut être analysé comme une déclaration sera analysé comme un , 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 les expressions-déclarations et les déclarations : une expression-instruction avec une conversion de type explicite de style fonction (5.2.3) car sa sous-expression la plus à gauche peut être distinguée d'une déclaration où le premier déclarant commence par un (. Dans ces cas, l'instruction est une déclaration .
8.2 Résolution d'ambiguïté [dcl.ambig.res]
1 L'ambiguïté résultant de la similitude entre une distribution de style fonction et une déclaration mentionnée en 6.8 peut également se produire dans le contexte d'une déclaration . Dans ce contexte, le choix se fait entre une déclaration de fonction avec un jeu redondant de parenthèses autour d'un nom de paramètre et une déclaration d'objet avec un style de fonction transtypé 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 une déclaration . [Remarque: Une déclaration peut être explicitement désambiguïsée par une conversion de style non fonctionnel, par un = pour indiquer l'initialisation ou en supprimant les parenthèses redondantes autour du nom du paramètre. —Fin 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
}
—Fin exemple]
Un exemple célèbre de ceci est le Analyseur le plus vexant , un nom popularisé par Scott Meyers dans le point 6 de son STL efficace livre:
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:
dataFile
. Son type est istream_iterator<int>
. Les parenthèses autour de dataFile
sont superflues et sont ignorées.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'accolade qui permet de contourner de tels problèmes d'analyse dans de nombreux contextes.
decltype
Contrairement à la déduction de type auto
, 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 notédecltype(e)
est défini comme suit:- si
e
est une expression id sans parenthèse ou un accès de membre de classe sans parenthèse (5.2.5),decltype(e)
est le type de l'entité nommée pare
. S'il n'y a pas une telle entité, ou sie
nomme un ensemble de fonctions surchargées, le programme est mal formé;- sinon, si
e
est une valeur x,decltype(e)
estT&&
, OùT
est le type dee
;- sinon, si
e
est une valeur l,decltype(e)
estT&
, OùT
est le type dee
;- sinon,
decltype(e)
est le type dee
.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. —Fin 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 FAQ C++ et ce sujet Q&R
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
.
Il existe une multitude de subtilités avec des macros de préprocesseur dans leur interaction avec le langage C++ proprement dit, dont les plus courantes sont répertoriées ci-dessous
#define TIMES(A, B) (A) * (B);
afin d'éviter la priorité d'opérateur indésirable (par exemple dans TIMES(1 + 2, 2 + 1)
qui donne 9 mais donnerait 6 sans les parenthèses autour de (A)
et (B)
assert((std::is_same<int, int>::value));
qui autrement ne compilerait pas(min)(a, b)
(avec l'effet secondaire indésirable de désactiver également ADL)En général, dans les langages de programmation, les parenthèses "supplémentaires" impliquent qu'elles ne changent pas l'ordre syntaxique d'analyse ou la signification. Ils sont ajoutés pour clarifier l'ordre (priorité des opérateurs) au profit des personnes lisant le code, et leur seul effet serait de ralentir légèrement le processus de compilation et de réduire les erreurs humaines dans la compréhension du code (ce qui accélère probablement le processus de développement global ).
Si un ensemble de parenthèses change réellement la façon dont une expression est analysée, alors elles sont par définition pas extra. Les parenthèses qui transforment une analyse illégale/invalide en une analyse légale ne sont pas "supplémentaires", bien que puisse signaler une mauvaise conception du langage.