web-dev-qa-db-fra.com

Quand les parenthèses supplémentaires ont-elles un effet, autre que sur la priorité de l'opérateur?

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.

90
TemplateRex

TL; DR

Des parenthèses supplémentaires modifient 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 des contextes de liste
  • résolution de l'ambiguïté des analyses vexantes
  • déduire la référence dans les expressions decltype
  • prévention des erreurs de macro de préprocesseur

Empêcher la recherche de nom dépendante de l'argument

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]

Activation de l'opérateur virgule dans des 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'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]

Résolution de l'ambiguïté des analyses vexantes

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:

  • Le premier paramètre est nommé dataFile. Son type est istream_iterator<int>. Les parenthèses autour de dataFile sont superflues et sont ignorées.
  • Le deuxième paramètre n'a pas de nom. Son type est un pointeur sur une fonction ne prenant rien et renvoyant un 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.

Déduire la référence dans les expressions 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 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. —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.

Prévention des erreurs liées aux macros de préprocesseur

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

  • utiliser 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 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)
  • en utilisant des parenthèses autour des arguments de macro ayant des virgules à l'intérieur: assert((std::is_same<int, int>::value)); qui autrement ne compilerait pas
  • utiliser des parenthèses autour d'une fonction pour se 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)
109
TemplateRex

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.

4
Phil Perry