Quando le parentesi in più hanno un effetto, oltre alla precedenza degli operatori?


91

Le parentesi in C ++ sono usate in molti posti: ad esempio nelle chiamate di funzione e nelle espressioni di raggruppamento per sovrascrivere la precedenza degli operatori. A parte le parentesi extra illegali (come intorno agli elenchi di argomenti delle chiamate di funzione), una regola generale, ma non assoluta, del C ++ è che le parentesi extra non fanno mai male :

5.1 Espressioni primarie [expr.prim]

5.1.1 Generale [expr.prim.general]

6 Un'espressione tra parentesi è un'espressione primaria il cui tipo e valore sono identici a quelli dell'espressione racchiusa. La presenza di parentesi non influisce sul fatto che l'espressione sia un lvalue. L'espressione tra parentesi può essere utilizzata esattamente negli stessi contesti di quelli in cui è possibile utilizzare l'espressione racchiusa e con lo stesso significato, salvo diversamente indicato .

Domanda : in quali contesti le parentesi extra cambiano il significato di un programma C ++, oltre a sovrascrivere la precedenza degli operatori di base?

NOTA : Considero la restrizione della sintassi del puntatore a membro&qualified-id senza parentesi al di fuori dell'ambito perché limita la sintassi piuttosto che consentire due sintassi con significati diversi. Allo stesso modo, l'uso di parentesi all'interno delle definizioni di macro del preprocessore protegge anche dalla precedenza degli operatori indesiderati.


"Considero la risoluzione & (id-qualificato) per il puntatore a un membro un'applicazione della precedenza degli operatori." -- Perché? Se ometti le parentesi in &(C::f), l'operando di &è fermo C::f, non è vero?

@hvd expr.unary.op/4: un puntatore a membro viene formato solo quando &viene utilizzato un esplicito e il suo operando è un id qualificato non racchiuso tra parentesi.
TemplateRex

Bene, quindi cosa ha a che fare con la precedenza degli operatori? (Non importa, la tua domanda modificata lo chiarisce.)

@hvd aggiornato, stavo confondendo l'RHS con l'LHS in questa domanda e risposta , e lì le parentesi sono usate per sovrascrivere la precedenza della chiamata di funzione ()sul selettore del puntatore a membro::*
TemplateRex

1
Penso che dovresti essere un po 'più preciso su quali casi devono essere presi in considerazione. Ad esempio, le parentesi attorno al nome di un tipo per renderlo un operatore di cast in stile C (qualunque sia il contesto) non creano affatto un'espressione tra parentesi. D'altra parte direi tecnicamente la condizione dopo se o while è un'espressione tra parentesi, ma poiché le parentesi fanno parte della sintassi qui non dovrebbero essere considerate. Né dovrebbe esserlo in nessun caso IMO, dove senza le parentesi l'espressione non sarebbe più analizzata come una singola unità, indipendentemente dal fatto che sia coinvolta o meno la precedenza degli operatori.
Marc van Leeuwen

Risposte:


112

TL; DR

Le parentesi aggiuntive cambiano il significato di un programma C ++ nei seguenti contesti:

  • impedendo la ricerca del nome dipendente dall'argomento
  • abilitare l'operatore virgola nei contesti elenco
  • risoluzione dell'ambiguità di analisi fastidiose
  • dedurre la referenzialità nelle decltypeespressioni
  • prevenire errori di macro del preprocessore

Prevenzione della ricerca del nome dipendente dall'argomento

Come dettagliato nell'Allegato A dello Standard, a post-fix expressiondel modulo (expression)è a primary expression, ma non an id-expression, e quindi non an unqualified-id. Ciò significa che la ricerca del nome dipendente dall'argomento è impedita nelle chiamate di funzione del modulo (fun)(arg)rispetto al modulo convenzionale fun(arg).

3.4.2 Ricerca del nome dipendente dall'argomento [basic.lookup.argdep]

1 Quando l'espressione postfissa in una chiamata di funzione (5.2.2) è un id non qualificato, è possibile cercare altri spazi dei nomi non considerati durante la consueta ricerca non qualificata (3.4.1), e in questi spazi dei nomi, la funzione si possono trovare dichiarazioni di modelli di funzione (11.3) non altrimenti visibili. Queste modifiche alla ricerca dipendono dai tipi di argomenti (e per gli argomenti del modello del modello, lo spazio dei nomi dell'argomento del modello). [ Esempio:

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 esempio]

Abilitazione dell'operatore virgola nei contesti di elenco

L'operatore virgola ha un significato speciale nella maggior parte dei contesti simili a elenchi (argomenti di funzioni e modelli, elenchi di inizializzatori ecc.). Le parentesi del modulo a, (b, c), din tali contesti possono abilitare l'operatore virgola rispetto alla forma regolare in a, b, c, dcui l'operatore virgola non si applica.

5.18 Operatore virgola [expr.comma]

2 Nei contesti in cui alla virgola viene assegnato un significato speciale, [Esempio: negli elenchi di argomenti delle funzioni (5.2.2) e negli elenchi di inizializzatori (8.5) —end esempio] l'operatore virgola come descritto nella clausola 5 può apparire solo tra parentesi. [ Esempio:

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

ha tre argomenti, il secondo dei quali ha valore 5. —end esempio]

Risoluzione di ambiguità di analisi fastidiose

La retrocompatibilità con C e la sua sintassi di dichiarazione di funzione arcana può portare a sorprendenti ambiguità di analisi, note come analisi fastidiose. In sostanza, tutto ciò che può essere analizzato come dichiarazione verrà analizzato come uno , anche se si applicherebbe anche un'analisi concorrente.

6.8 Risoluzione dell'ambiguità [stmt.ambig]

1 Esiste un'ambiguità nella grammatica che coinvolge dichiarazioni-espressioni e dichiarazioni : un'istruzione-espressione con una conversione di tipo esplicita in stile funzione (5.2.3) come sottoespressione più a sinistra può essere indistinguibile da una dichiarazione in cui il primo dichiaratore inizia con un ( . In questi casi la dichiarazione è una dichiarazione .

8.2 Risoluzione dell'ambiguità [dcl.ambig.res]

1 L'ambiguità derivante dalla somiglianza tra un cast in stile funzione e una dichiarazione menzionata in 6.8 può verificarsi anche nel contesto di una dichiarazione . In quel contesto, la scelta è tra una dichiarazione di funzione con un insieme ridondante di parentesi attorno a un nome di parametro e una dichiarazione di oggetto con un cast in stile funzione come inizializzatore. Proprio come per le ambiguità menzionate in 6.8, la risoluzione è di considerare qualsiasi costrutto che potrebbe essere una dichiarazione una dichiarazione . [Nota: una dichiarazione può essere disambiguata esplicitamente da un cast in stile non funzionale, da un = per indicare l'inizializzazione o rimuovendo le parentesi ridondanti attorno al nome del parametro. —End note] [Esempio:

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 esempio]

Un famoso esempio di questo è The Most Vexing Parse , un nome reso popolare da Scott Meyers nell'articolo 6 del suo libro Efficace 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

Questo dichiara una funzione,, il datacui tipo restituito è list<int>. I dati della funzione accettano due parametri:

  • Il primo parametro è denominato dataFile. Il suo tipo è istream_iterator<int>. Le parentesi intorno dataFilesono superflue e vengono ignorate.
  • Il secondo parametro non ha nome. Il suo tipo è un puntatore a una funzione che non prende nulla e restituisce un file istream_iterator<int>.

Mettere parentesi extra attorno al primo argomento della funzione (le parentesi attorno al secondo argomento sono illegali) risolverà l'ambiguità

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

C ++ 11 ha una sintassi di inizializzazione delle parentesi graffe che consente di eseguire il side-step di tali problemi di analisi in molti contesti.

Dedurre la referenzialità nelle decltypeespressioni

Contrariamente alla autodeduzione di tipo, decltypeconsente di dedurre la referenzialità (riferimenti lvalue e rvalue). Le regole distinguono tra decltype(e)ed decltype((e))espressioni:

7.1.6.2 Indicatori di tipo semplice [dcl.type.simple]

4 Per un'espressione e, il tipo indicato dadecltype(e) è definito come segue:

- se eè un'espressione id senza parentesi o un accesso a un membro della classe senza parentesi (5.2.5), decltype(e)è il tipo dell'entità chiamata da e. Se non esiste una tale entità, o se enomina un insieme di funzioni sovraccaricate, il programma è mal formato;

- altrimenti, se eè un xvalue, decltype(e)è T&&, dove Tè il tipo di e;

- altrimenti, se eè un lvalue, decltype(e)è T&, dove Tè il tipo di e;

- altrimenti, decltype(e)è il tipo di e.

L'operando dello specificatore decltype è un operando non valutato (clausola 5). [ Esempio:

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&

—End esempio] [Nota: le regole per determinare i tipi che coinvolgono decltype(auto)sono specificate in 7.1.6.4. —End nota]

Le regole per decltype(auto)hanno un significato simile per parentesi extra nell'RHS dell'espressione di inizializzazione. Ecco un esempio tratto dalle domande frequenti su C ++ e dalle domande e risposte correlate

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

Il primo restituisce string, il secondo restituisce string &, che è un riferimento alla variabile locale str.

Prevenzione degli errori relativi alla macro del preprocessore

C'è una miriade di sottigliezze con le macro del preprocessore nella loro interazione con il linguaggio C ++ vero e proprio, le più comuni delle quali sono elencate di seguito

  • utilizzando le parentesi attorno ai parametri della macro all'interno della definizione della macro #define TIMES(A, B) (A) * (B);al fine di evitare la precedenza di operatori indesiderati (ad esempio in TIMES(1 + 2, 2 + 1)cui restituisce 9 ma restituisce 6 senza le parentesi intorno (A)e(B)
  • usando le parentesi intorno agli argomenti macro che contengono virgole: assert((std::is_same<int, int>::value));che altrimenti non verrebbero compilati
  • utilizzo di parentesi attorno a una funzione per proteggere dall'espansione di macro nelle intestazioni incluse: (min)(a, b)(con l'effetto collaterale indesiderato di disabilitare anche ADL)

7
Non cambia realmente il significato del programma, ma la migliore pratica e influisce sugli avvertimenti emessi dal compilatore: parentesi extra dovrebbero essere usate in if/ whilese l'espressione è un assegnamento. Ad esempio if (a = b)- avvertimento (intendevi ==?), Mentre if ((a = b))- nessun avvertimento.
Csq

@Csq grazie, buona osservazione, ma questo è un avvertimento da parte di un particolare compilatore e non richiesto dallo Standard. Non credo che si adatti alla natura linguistica di questa domanda e risposta.
TemplateRex

Fa (min)(a, b)(con il male MACRO min(A, B)) fa parte della prevenzione ricerca del nome argomento-dipendente?
Jarod42

@ Jarod42 Immagino di sì, ma consideriamo queste e altre macro malvagie al di fuori dell'ambito della domanda :-)
TemplateRex

5
@JamesKanze: Nota che OP e TemplateRex sono la stessa persona ^ _ ^
Jarod42

4

In generale, nei linguaggi di programmazione, tra parentesi "extra" implica che essi siano , non cambiando l'ordine di analisi sintattica o un significato. Vengono aggiunti per chiarire l'ordine (precedenza degli operatori) a beneficio delle persone che leggono il codice e il loro unico effetto sarebbe quello di rallentare leggermente il processo di compilazione e ridurre gli errori umani nella comprensione del codice (probabilmente accelerando il processo di sviluppo complessivo ).

Se un insieme di parentesi cambia effettivamente il modo in cui un'espressione viene analizzata, allora per definizione non sono extra. Le parentesi che trasformano un'analisi illegale / non valida in una legale non sono "extra", sebbene ciò possa indicare un linguaggio scadente.


2
esattamente, e questa è la regola generale anche in C ++ (vedere la citazione Standard nella domanda), salvo diversamente indicato . Evidenziare questi "punti deboli" era lo scopo di questa domanda e risposta.
TemplateRex
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.