Il mio tentativo di inizializzazione del valore viene interpretato come una dichiarazione di funzione e perché A non (()); risolvilo?


158

Tra le molte cose che Stack Overflow mi ha insegnato c'è quella che è conosciuta come "l'analisi più irritante", che è classicamente dimostrata con una linea come

A a(B()); //declares a function

Mentre questo, per la maggior parte, sembra intuitivamente essere la dichiarazione di un oggetto adi tipo A, prendendo un Boggetto temporaneo come parametro del costruttore, in realtà è una dichiarazione di una funzione che arestituisce un A, portando un puntatore a una funzione che ritorna Be non accetta parametri . Allo stesso modo la linea

A a(); //declares a function

rientra anche nella stessa categoria, poiché invece di un oggetto, dichiara una funzione. Ora, nel primo caso, la solita soluzione per questo problema è quella di aggiungere un set aggiuntivo di parentesi / parentesi attorno a B(), poiché il compilatore lo interpreterà come la dichiarazione di un oggetto

A a((B())); //declares an object

Tuttavia, nel secondo caso, fare lo stesso porta a un errore di compilazione

A a(()); //compile error

La mia domanda è: perché? Sì, sono ben consapevole del fatto che la "soluzione alternativa" corretta è cambiarlo in A a;, ma sono curioso di sapere cosa fa il supplemento ()per il compilatore nel primo esempio che poi non funziona quando lo riapplica in il secondo esempio. La A a((B()));soluzione alternativa è un'eccezione specifica scritta nello standard?


20
(B())è solo un'espressione C ++, niente di più. Non è alcun tipo di eccezione. L'unica differenza che fa è che non c'è modo che possa essere analizzato come un tipo, e quindi non lo è.
Pavel Minaev,

12
Va anche notato che il secondo caso nonA a(); appartiene alla stessa categoria. Per il compilatore , non esiste mai un modo diverso di analizzarlo: un inizializzatore in quel punto non consiste mai di parentesi vuote, quindi questa è sempre una dichiarazione di funzione.
Johannes Schaub - litb,

11
il punto eccellente di litb è sottile ma importante e vale la pena sottolineare: il motivo per cui esiste l'ambiguità in questa dichiarazione "A a (B ())" sta nell'analisi di "B ()" -> può essere sia un'espressione che una dichiarazione e il compilatore devono 'selezionare' decl su expr - quindi se B () è un decl, allora 'a' può essere solo un decl di funzione (non un decl variabile). Se '()' fosse autorizzato a essere un inizializzatore 'A a ()' sarebbe ambiguo - ma non expr vs decl, ma var decl vs func decl - non vi è alcuna regola che preferisca un decl a un altro - e quindi '() "qui non è consentito come inizializzatore - e l'ambiguità non aumenta.
Faisal Vali,

6
A a();non è un esempio dell'analisi più irritante . È semplicemente una dichiarazione di funzione, proprio come in C.
Pete Becker,

2
"la" soluzione alternativa "corretta è cambiarla in A a;" è errata. Ciò non ti darà l'inizializzazione di un tipo POD. Per ottenere la inizializzazione scrivere A a{};.
Saluti e hth. - Alf

Risposte:


70

Non c'è una risposta illuminata, è solo perché non è definita come sintassi valida dal linguaggio C ++ ... Quindi è così, per definizione del linguaggio.

Se hai un'espressione dentro, allora è valida. Per esempio:

 ((0));//compiles

In parole povere: perché (x)è un'espressione C ++ valida, mentre ()non lo è.

Per saperne di più su come sono definite le lingue e su come funzionano i compilatori, dovresti conoscere la teoria del linguaggio formale o più specificamente Grammatiche senza contesto (CFG) e materiali correlati come le macchine a stati finiti. Se sei interessato a questo, anche se le pagine di Wikipedia non saranno sufficienti, dovrai procurarti un libro.


45
In parole povere: perché (x)è un'espressione C ++ valida, mentre ()non lo è.
Pavel Minaev,

Ho accettato questa risposta, inoltre il commento di Pavel alla mia domanda iniziale mi ha aiutato molto
GRB


29

Dichiaratori di funzioni C.

Prima di tutto, c'è C. In C, A a()è la dichiarazione di funzione. Ad esempio, putcharha la seguente dichiarazione. Normalmente, tali dichiarazioni vengono archiviate in file di intestazione, tuttavia nulla impedisce di scriverle manualmente, se si conosce l'aspetto della dichiarazione di funzione. I nomi degli argomenti sono facoltativi nelle dichiarazioni, quindi in questo esempio l'ho omesso.

int putchar(int);

Questo ti permette di scrivere il codice in questo modo.

int puts(const char *);
int main() {
    puts("Hello, world!");
}

C consente inoltre di definire funzioni che accettano funzioni come argomenti, con una sintassi leggibile che assomiglia a una chiamata di funzione (beh, è ​​leggibile, a patto che non si restituisca un puntatore alla funzione).

#include <stdio.h>

int eighty_four() {
    return 84;
}

int output_result(int callback()) {
    printf("Returned: %d\n", callback());
    return 0;
}

int main() {
    return output_result(eighty_four);
}

Come ho già detto, C consente di omettere i nomi degli argomenti nei file di intestazione, quindi output_resultsarebbe simile a questo nei file di intestazione.

int output_result(int());

Un argomento nel costruttore

Non lo riconosci? Bene, lascia che te lo ricordi.

A a(B());

Sì, è esattamente la stessa dichiarazione di funzione. Aè int, aè output_resulted Bè int.

Si può facilmente notare un conflitto di C con le nuove funzionalità di C ++. Per essere precisi, i costruttori sono il nome della classe e la parentesi e la sintassi della dichiarazione alternativa con ()anziché =. In base alla progettazione, C ++ cerca di essere compatibile con il codice C, e quindi deve occuparsi di questo caso, anche se praticamente a nessuno importa. Pertanto, le vecchie funzionalità C hanno la priorità sulle nuove funzionalità C ++. La grammatica delle dichiarazioni cerca di far corrispondere il nome come funzione, prima di tornare alla nuova sintassi con ()se fallisce.

Se una di queste funzionalità non esistesse o avesse una sintassi diversa (come {}in C ++ 11), questo problema non si sarebbe mai verificato per la sintassi con un argomento.

Ora potresti chiedere perché A a((B()))funziona. Bene, dichiariamo output_resulttra parentesi inutili.

int output_result((int()));

Non funzionerà La grammatica richiede che la variabile non sia tra parentesi.

<stdin>:1:19: error: expected declaration specifiers or ‘...’ before ‘(’ token

Tuttavia, C ++ prevede un'espressione standard qui. In C ++, puoi scrivere il seguente codice.

int value = int();

E il seguente codice.

int value = ((((int()))));

Il C ++ prevede che l'espressione all'interno delle parentesi sia ... beh ... espressione, al contrario del tipo C previsto. Le parentesi non significano nulla qui. Tuttavia, inserendo parentesi inutili, la dichiarazione della funzione C non viene abbinata e la nuova sintassi può essere abbinata correttamente (il che si aspetta semplicemente un'espressione, come 2 + 2).

Più argomenti nel costruttore

Sicuramente un argomento è carino, ma per quanto riguarda due? Non è che i costruttori possano avere solo un argomento. Una delle classi integrate che accetta due argomenti èstd::string

std::string hundred_dots(100, '.');

Questo va bene e va bene (tecnicamente, avrebbe parso più irritante se fosse scritto come std::string wat(int(), char()), ma siamo onesti - chi lo scriverebbe? Ma supponiamo che questo codice abbia un problema fastidioso. Supponiamo che tu debba mettere tutto tra parentesi.

std::string hundred_dots((100, '.'));

Non proprio così.

<stdin>:2:36: error: invalid conversion from char to const char*’ [-fpermissive]
In file included from /usr/include/c++/4.8/string:53:0,
                 from <stdin>:1:
/usr/include/c++/4.8/bits/basic_string.tcc:212:5: error:   initializing argument 1 of std::basic_string<_CharT, _Traits, _Alloc>::basic_string(const _CharT*, const _Alloc&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]’ [-fpermissive]
     basic_string<_CharT, _Traits, _Alloc>::
     ^

Io non sono sicuro perché g ++ tentativi di convertire chara const char *. In entrambi i casi, il costruttore è stato chiamato con un solo valore di tipo char. Non c'è sovraccarico che ha un argomento di tipo char, quindi il compilatore è confuso. Potresti chiedere: perché l'argomento è di tipo char?

(100, '.')

Sì, ,ecco un operatore virgola. L'operatore virgola accetta due argomenti e fornisce l'argomento di destra. Non è davvero utile, ma è qualcosa che deve essere conosciuto per la mia spiegazione.

Invece, per risolvere l'analisi più irritante, è necessario il seguente codice.

std::string hundred_dots((100), ('.'));

Gli argomenti sono tra parentesi, non l'intera espressione. In effetti, solo una delle espressioni deve essere racchiusa tra parentesi, poiché è sufficiente uscire leggermente dalla grammatica C per utilizzare la funzione C ++. Le cose ci portano al punto di zero argomenti.

Zero argomenti nel costruttore

Potresti aver notato la eighty_fourfunzione nella mia spiegazione.

int eighty_four();

Sì, questo è influenzato anche dall'analisi più fastidiosa. È una definizione valida e molto probabilmente hai visto se hai creato i file di intestazione (e dovresti). L'aggiunta di parentesi non lo risolve.

int eighty_four(());

Perchè è così? Bene, ()non è un'espressione. In C ++, devi inserire un'espressione tra parentesi. Non puoi scrivere auto value = ()in C ++, perché ()non significa nulla (e anche se lo fosse, come una tupla vuota (vedi Python), sarebbe un argomento, non zero). In pratica ciò significa che non è possibile utilizzare la sintassi abbreviata senza utilizzare la sintassi di C ++ 11 {}, poiché non ci sono espressioni da mettere tra parentesi e si applicherà sempre la grammatica C per le dichiarazioni di funzione.


12

Potresti invece

A a(());

uso

A a=A();

32
La "soluzione alternativa migliore" non è equivalente. int a = int();inizializza acon 0, int a;lascia anon inizializzata. Una soluzione alternativa corretta è quella di utilizzare gli A a = {};aggregati, A a;quando l'inizializzazione predefinita fa quello che vuoi, e A a = A();in tutti gli altri casi - o semplicemente usare in modo A a = A();coerente. In C ++ 11, basta usareA a {};
Richard Smith il

6

Le parentesi più interne nel tuo esempio sarebbero un'espressione, e in C ++ la grammatica definisce un'essere expressionuna assignment-expressiono un'altra expressionseguita da una virgola e un'altra assignment-expression(Appendice A.4 - Riepilogo grammaticale / Espressioni).

La grammatica definisce inoltre assignment-expressioncome uno di molti altri tipi di espressione, nessuno dei quali può essere nulla (o solo spazi bianchi).

Quindi la ragione per cui non puoi avere A a(())è semplicemente perché la grammatica non lo consente. Tuttavia, non posso rispondere al motivo per cui le persone che hanno creato C ++ non hanno consentito questo particolare utilizzo di parentesi vuote come una sorta di caso speciale - immagino che preferirebbero non inserire un caso così speciale se ci fosse un'alternativa ragionevole.

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.