91 votes

Quand les parenthèses supplémentaires ont-elles un effet autre que celui de la préséance des opérateurs ?

En C++, les parenthèses sont utilisées à de nombreux endroits : par exemple, dans les appels de fonction et les expressions de regroupement pour passer outre la préséance des opérateurs. En dehors des parenthèses supplémentaires illégales (comme autour des listes d'arguments d'appel de fonction), une règle générale - mais non absolue - du C++ est que des parenthèses supplémentaires ne font jamais de mal :

5.1 Expressions primaires [expr.prim]

5.1.1 Généralités [expr.prim.general]

6 Une expression mise entre parenthèses est une expression primaire dont le type et le valeur sont identiques à ceux de l'expression entre parenthèses. La présence de parenthèses n'affecte pas le fait que l'expression soit une lvalue. L'expression entre parenthèses peut être utilisée dans exactement les mêmes contextes que ceux dans lesquels l'expression encadrée peut être utilisée, et avec la même signification, sauf indication contraire .

Question Dans quels contextes des parenthèses supplémentaires changent-elles la signification d'un programme C++, en dehors de la préséance des opérateurs de base ?

NOTE : Je considère la restriction de pointeur vers le membre syntaxe pour &qualified-id sans parenthèses pour être en dehors de la portée parce qu'elle restreint la syntaxe plutôt que d'autoriser deux syntaxes ayant des significations différentes. De même, l'utilisation de parenthèses à l'intérieur des définitions des macros du préprocesseur permet également de se prémunir contre une préséance indésirable des opérateurs.

110voto

TemplateRex Points 26447

TL;DR

Les parenthèses supplémentaires modifient la signification d'un programme C++ dans les contextes suivants :

  • empêcher la recherche de noms en fonction des arguments
  • activer l'opérateur virgule dans les contextes de liste
  • résolution d'ambiguïtés de parses contrariants
  • déduire la référentialité dans decltype expressions
  • prévention des erreurs de macro du préprocesseur

Empêcher la recherche de noms en fonction des arguments

Comme le précise l'annexe A de la norme, une post-fix expression de la forme (expression) est un primary expression mais pas un id-expression et donc pas un unqualified-id . Cela signifie que la recherche de noms dépendante des arguments 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épendant de l'argument [basic.lookup.argdep]

1 Quand l'expression postfixe dans un appel de fonction (5.2.2) est un unqualified-id d'autres espaces de noms qui n'ont pas été pris en compte lors de l'examen habituel de l'UE. habituelle (3.4.1) peuvent être recherchés, et dans ces espaces de noms, les déclarations de fonctions amies ou de modèles de fonctions de l'espace de nommage (11.3) non visibles autrement peuvent être trouvées. Ces modifications de la recherche recherche dépendent des types d'arguments (et pour les arguments de gabarit de fonction l'espace de nom de l'argument du 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 de l'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 fonctions et de modèles, listes d'initialisateurs, etc.) Les parenthèses de la forme a, (b, c), d dans de tels contextes peut 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 particulière, [ Exemple : sur les listes d'arguments de fonctions (5.2.2) et les listes d'initialisateurs (8.5) -fin de l'exemple ] l'opérateur de virgule tel que décrit dans la clause 5 peut apparaître uniquement entre parenthèses. [ Exemple :

f(a, (t=3, t+2), c);

a trois arguments, dont le deuxième a la valeur 5. -fin exemple ]

Résolution de l'ambiguïté des syntagmes contrariants

La rétrocompatibilité avec le C et sa syntaxe de déclaration de fonction obscure peut conduire à des ambiguïtés d'analyse surprenantes, connues sous le nom d'analyses vexantes. Essentiellement, tout ce qui peut être analysé comme une déclaration sera analysé comme une déclaration même si une analyse syntaxique concurrente s'appliquerait également.

6.8 Résolution des ambiguïtés [stmt.ambig].

1 Il y a une ambiguïté dans la grammaire impliquant les déclarations d'expression et les déclarations : Un énoncé d'expression avec une fonction de style de type fonction (5.2.3) dans sa sous-expression la plus à gauche peut être être indiscernable d'une déclaration dont le premier déclarant commence par un avec 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é découlant de la similitude entre une fonction de type et une déclaration mentionnée en 6.8 peut également se produire dans le contexte de d'une déclaration . Dans ce contexte, le choix se fait entre une fonction avec une série redondante de parenthèses autour d'un nom de paramètre et une déclaration d'objet avec un cast de style fonction en tant qu'initialisateur. initialisateur. Comme pour les ambiguïtés mentionnées en 6.8, la méthode de résolution consiste à considérer toute construction qui pourrait être une déclaration une déclaration . [Note : Une déclaration peut être explicitement explicitement par un cast de style non fonctionnel, par un = pour indiquer l'initialisation ou en supprimant les parenthèses redondantes autour du nom du paramètre. 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 de l'exemple ]

Un exemple célèbre de ce phénomène est le Parse la plus vexante un nom popularisé par Scott Meyers dans l'article 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

Ceci déclare une fonction, data dont le type de retour est list<int> . La fonction data prend deux paramètres :

  • Le premier paramètre est nommé dataFile . Son type est istream_iterator<int> . Le site parenthèses autour de dataFile sont superflus et sont ignorés.
  • Le deuxième paramètre n'a pas de nom. Son type est un pointeur vers une fonction prenant rien et retourne un istream_iterator<int> .

En plaçant des parenthèses supplémentaires autour du premier argument de la fonction (les parenthèses autour du deuxième argument sont illégales), l'ambiguïté sera résolue.

list<int> data((istream_iterator<int>(dataFile)), // note new parens
                istream_iterator<int>());          // around first argument
                                                  // to list's constructor

Le C++11 dispose d'une syntaxe d'initialisation des accolades qui permet de contourner ces problèmes d'analyse dans de nombreux contextes.

Déduction de la référentialité dans decltype expressions

Contrairement à auto déduction de type, decltype permet de déduire la référentialité (références lvalue et rvalue). Les règles distinguent entre decltype(e) y decltype((e)) expressions :

7.1.6.2 Spécificateurs de type simples [dcl.type.simple]

4 Pour une expression e , le type désigné par decltype(e) est défini comme suit suivante :

- si e est une expression id non parenthésée ou un accès à un membre de classe non parenthésé (5.2.5), decltype(e) est le type de l'entité nommée par e . Si une telle entité n'existe pas, ou si e noms a ensemble de fonctions surchargées, le programme est mal formé ;

- sinon, si e est une valeur x, decltype(e) es T&& , donde T est le type de e ;

- sinon, si e est une lvalue, decltype(e) es T& , donde T est le type de e ;

- autrement, decltype(e) est le type de e .

L'opérande de l'option decltype est un opérande non évalué (Clause 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 de l'exemple ] [ Note : Les règles pour déterminer les types impliquant decltype(auto) sont spécifiés dans 7.1.6.4. -dernière 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 tiré du C++FAQ y cette question connexe

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

La première retourne string le second renvoie string & qui est une référence à la variable locale str .

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

Les macros du préprocesseur présentent une multitude de subtilités dans leur interaction avec le langage C++ proprement dit, dont les plus courantes sont énumérées ci-dessous.

  • l'utilisation de parenthèses autour des paramètres de la macro à l'intérieur de la définition de la macro #define TIMES(A, B) (A) * (B); afin d'éviter une préséance d'opérateur non souhaitée (par exemple dans TIMES(1 + 2, 2 + 1) ce qui donne 9 mais donnerait 6 sans les parenthèses autour de (A) y (B)
  • l'utilisation de parenthèses autour des arguments de macro ayant des virgules à l'intérieur : assert((std::is_same<int, int>::value)); qui autrement ne serait pas compilé
  • l'utilisation de 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 non désiré d'invalider également ADL)

4voto

Phil Perry Points 1574

En général, dans les langages de programmation, les parenthèses "supplémentaires" impliquent qu'elles sont pas modifier l'ordre d'analyse syntaxique ou le sens. Ils sont ajoutés pour clarifier l'ordre (préséance des opérateurs) au profit des personnes qui lisent 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érerait probablement le processus de développement global).

Si un ensemble de parenthèses cambia la manière dont une expression est analysée, alors ils sont par définition pas supplémentaire. Les parenthèses qui transforment une analyse illégale/invalide en une analyse légale ne sont pas "supplémentaires", bien que cela ne soit pas le cas. mai signaler une mauvaise conception de la langue.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X