Mein Versuch der Wertinitialisierung wird als Funktionsdeklaration interpretiert, und warum nicht A a (()); löse es?

158

Unter den vielen Dingen, die mir Stack Overflow beigebracht hat, befindet sich die sogenannte "ärgerlichste Analyse", die klassisch mit einer Linie wie z

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

Während dies für die meisten, intuitiv die Deklaration eines Objekts zu sein scheint , avom Typ A, eine temporäre Aufnahme BObjekt als Konstruktorparameter, ist es tatsächlich eine Erklärung einer Funktion aeiner Rückkehr A, einen Zeiger auf eine Funktion , wobei die zurückkehrt Bund sich keine Parameter . Ebenso die Linie

A a(); //declares a function

fällt ebenfalls unter dieselbe Kategorie, da anstelle eines Objekts eine Funktion deklariert wird. Im ersten Fall besteht die übliche Problemumgehung für dieses Problem darin, einen zusätzlichen Satz von Klammern / Klammern um das zu setzen B(), da der Compiler ihn dann als Deklaration eines Objekts interpretiert

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

Im zweiten Fall führt dies jedoch zu einem Kompilierungsfehler

A a(()); //compile error

Meine Frage ist, warum? Ja, ich bin mir sehr wohl bewusst, dass die richtige 'Problemumgehung' darin besteht, sie zu ändern A a;, aber ich bin gespannt, was das Extra ()für den Compiler im ersten Beispiel bewirkt, das dann beim erneuten Anwenden nicht funktioniert das zweite Beispiel. Ist die A a((B()));Problemumgehung eine bestimmte Ausnahme, die in den Standard geschrieben wurde?

GRB
quelle
20
(B())ist nur ein C ++ - Ausdruck, nichts weiter. Es ist keine Ausnahme. Der einzige Unterschied besteht darin, dass es unmöglich ist, es als Typ zu analysieren, und das ist es auch nicht.
Pavel Minaev
12
Es sollte auch beachtet werden , dass der zweite Fall A a();ist nicht von der gleichen Kategorie. Für den Compiler gibt es keine andere Möglichkeit, ihn zu analysieren: Ein Initialisierer an dieser Stelle besteht niemals aus leeren Klammern, daher ist dies immer eine Funktionsdeklaration.
Johannes Schaub - litb
11
Der ausgezeichnete Punkt von litb ist subtil, aber wichtig und sollte hervorgehoben werden - der Grund, warum die Mehrdeutigkeit in dieser Erklärung 'A a (B ())' besteht, liegt in der Analyse von 'B ()' -> es kann sowohl ein Ausdruck als auch sein Eine Deklaration und der Compiler müssen Deklaration über Ausdruck 'auswählen'. Wenn also B () eine Deklaration ist, kann 'a' nur eine Funktionsdeklaration sein (keine variable Deklaration). Wenn '()' ein Initialisierer sein dürfe, wäre 'A a ()' mehrdeutig - aber nicht expr vs decl, sondern var dec vs func decl - es gibt keine Regel, eine Deklaration einer anderen vorzuziehen - und so '() 'ist hier einfach nicht als Initialisierer erlaubt - und die Mehrdeutigkeit steigt nicht.
Faisal Vali
6
A a();ist kein Beispiel für die ärgerlichste Analyse . Es ist einfach eine Funktionsdeklaration, genau wie in C.
Pete Becker
2
"Die richtige 'Problemumgehung' besteht darin, sie in A a;" zu ändern "ist falsch. Dadurch erhalten Sie keine Initialisierung eines POD-Typs. Um die Initialisierung zu erhalten, schreiben Sie A a{};.
Prost und hth. - Alf

Antworten:

70

Es gibt keine aufgeklärte Antwort, nur weil sie von der C ++ - Sprache nicht als gültige Syntax definiert wird ... So ist es auch, per Definition der Sprache.

Wenn Sie einen Ausdruck in sich haben, ist dieser gültig. Beispielsweise:

 ((0));//compiles

Noch einfacher ausgedrückt: weil (x)es sich um einen gültigen C ++ - Ausdruck handelt, während dies ()nicht der Fall ist.

Um mehr darüber zu erfahren, wie Sprachen definiert werden und wie Compiler funktionieren, sollten Sie etwas über die formale Sprachtheorie oder insbesondere über kontextfreie Grammatiken (CFG) und verwandtes Material wie Finite-State-Maschinen lernen . Wenn Sie daran interessiert sind, obwohl die Wikipedia-Seiten nicht ausreichen, müssen Sie sich ein Buch besorgen.

Brian R. Bondy
quelle
45
Noch einfacher ausgedrückt: weil (x)es sich um einen gültigen C ++ - Ausdruck handelt, während dies ()nicht der Fall ist.
Pavel Minaev
Ich habe diese Antwort akzeptiert, außerdem hat mir Pavel's Kommentar zu meiner ersten Frage sehr geholfen
GRB
29

C-Funktionsdeklaratoren

Zuallererst gibt es C. In C A a()ist Funktionsdeklaration. Hat zum Beispiel putchardie folgende Deklaration. Normalerweise werden solche Deklarationen in Header-Dateien gespeichert, aber nichts hindert Sie daran, sie manuell zu schreiben, wenn Sie wissen, wie die Funktionsdeklaration aussieht. Die Argumentnamen sind in Deklarationen optional, daher habe ich sie in diesem Beispiel weggelassen.

int putchar(int);

Auf diese Weise können Sie den Code wie folgt schreiben.

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

Mit C können Sie auch Funktionen definieren, die Funktionen als Argumente verwenden, mit einer gut lesbaren Syntax, die wie ein Funktionsaufruf aussieht (gut, sie ist lesbar, solange Sie keinen Zeiger auf die Funktion zurückgeben).

#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);
}

Wie bereits erwähnt, erlaubt C das Weglassen von Argumentnamen in Header-Dateien, daher output_resultwürde dies in Header- Dateien so aussehen.

int output_result(int());

Ein Argument im Konstruktor

Erkennst du das nicht? Nun, ich möchte Sie daran erinnern.

A a(B());

Ja, es ist genau die gleiche Funktionsdeklaration. Aist int, aist output_resultund Bist int.

Sie können leicht einen Konflikt von C mit neuen Funktionen von C ++ feststellen. Um genau zu sein, Konstruktoren sind Klassenname und Klammer und alternative Deklarationssyntax mit ()anstelle von =. C ++ versucht von Natur aus, mit C-Code kompatibel zu sein, und muss sich daher mit diesem Fall befassen - auch wenn es praktisch niemanden interessiert. Daher haben alte C-Funktionen Vorrang vor neuen C ++ - Funktionen. Die Grammatik der Deklarationen versucht, den Namen als Funktion abzugleichen, bevor sie zur neuen Syntax zurückkehrt, ()wenn dies fehlschlägt.

Wenn eine dieser Funktionen nicht vorhanden wäre oder eine andere Syntax hätte (wie {}in C ++ 11), wäre dieses Problem bei der Syntax mit einem Argument niemals aufgetreten.

Jetzt können Sie fragen, warum A a((B()))funktioniert. Nun, lassen Sie uns output_resultmit nutzlosen Klammern erklären .

int output_result((int()));

Es wird nicht funktionieren. Die Grammatik erfordert, dass die Variable nicht in Klammern steht.

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

C ++ erwartet hier jedoch einen Standardausdruck. In C ++ können Sie den folgenden Code schreiben.

int value = int();

Und der folgende Code.

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

C ++ erwartet, dass der Ausdruck in Klammern ... nun ja ... Ausdruck ist, im Gegensatz zu dem Typ, den C erwartet. Klammern bedeuten hier nichts. Durch Einfügen nutzloser Klammern wird die C-Funktionsdeklaration jedoch nicht abgeglichen, und die neue Syntax kann ordnungsgemäß abgeglichen werden (was lediglich einen Ausdruck erwartet, z. B. 2 + 2).

Weitere Argumente im Konstruktor

Sicherlich ist ein Argument nett, aber was ist mit zwei? Es ist nicht so, dass Konstruktoren nur ein Argument haben könnten. Eine der integrierten Klassen, die zwei Argumente akzeptiert, iststd::string

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

Das ist alles gut und schön (technisch gesehen wäre es am ärgerlichsten, wenn es so geschrieben würde std::string wat(int(), char()), aber seien wir ehrlich - wer würde das schreiben? Aber nehmen wir an, dieser Code hat ein ärgerliches Problem. Sie würden annehmen, dass Sie setzen müssen alles in Klammern.

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

Nicht ganz so.

<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>::
     ^

Ich bin mir nicht sicher , warum g ++ versucht zu konvertieren charzu const char *. In beiden Fällen wurde der Konstruktor mit nur einem Wert vom Typ aufgerufen char. Es gibt keine Überladung mit einem Argument vom Typ char, daher ist der Compiler verwirrt. Sie fragen sich vielleicht, warum das Argument vom Typ char ist.

(100, '.')

Ja, ,hier ist ein Kommaoperator. Der Kommaoperator akzeptiert zwei Argumente und gibt das Argument auf der rechten Seite an. Es ist nicht wirklich nützlich, aber es ist etwas, das für meine Erklärung bekannt sein muss.

Stattdessen wird der folgende Code benötigt, um die ärgerlichste Analyse zu lösen.

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

Die Argumente stehen in Klammern, nicht der gesamte Ausdruck. Tatsächlich muss nur einer der Ausdrücke in Klammern stehen, da es ausreicht, leicht von der C-Grammatik abzuweichen, um die C ++ - Funktion zu verwenden. Die Dinge bringen uns an den Punkt der Nullargumente.

Keine Argumente im Konstruktor

Möglicherweise haben Sie die eighty_fourFunktion in meiner Erklärung bemerkt .

int eighty_four();

Ja, dies wird auch von der ärgerlichsten Analyse beeinflusst. Es ist eine gültige Definition, die Sie höchstwahrscheinlich gesehen haben, wenn Sie Header-Dateien erstellt haben (und sollten). Das Hinzufügen von Klammern behebt das Problem nicht.

int eighty_four(());

Warum ist das so? Nun, ()ist kein Ausdruck. In C ++ müssen Sie einen Ausdruck in Klammern setzen. Sie können nicht auto value = ()in C ++ schreiben , da ()dies nichts bedeutet (und selbst wenn dies der Fall wäre, wie ein leeres Tupel (siehe Python), wäre es ein Argument, nicht Null). Praktisch bedeutet dies, dass Sie die Kurzschrift-Syntax nicht verwenden können, ohne die Syntax von C ++ 11 zu verwenden {}, da keine Ausdrücke in Klammern gesetzt werden müssen und die C-Grammatik für Funktionsdeklarationen immer gilt.

Konrad Borowski
quelle
12

Sie könnten stattdessen

A a(());

verwenden

A a=A();
user265149
quelle
32
Die "bessere Problemumgehung" ist nicht gleichwertig. int a = int();Initialisiert amit 0, int a;lässt anicht initialisiert. Eine korrekte Problemumgehung besteht darin, sie A a = {};für Aggregate zu verwenden, A a;wenn die Standardinitialisierung das tut, was Sie möchten, und A a = A();in allen anderen Fällen - oder einfach A a = A();konsistent zu verwenden. In C ++ 11 verwenden Sie einfachA a {};
Richard Smith
6

Die innersten Parens in Ihrem Beispiel wären ein Ausdruck, und in C ++ definiert die Grammatik ein expressionals das eine assignment-expressionoder andere, expressiongefolgt von einem Komma und einem anderen assignment-expression(Anhang A.4 - Grammatikzusammenfassung / Ausdrücke).

Die Grammatik definiert einen assignment-expressionals einen von mehreren anderen Ausdruckstypen, von denen keiner nichts (oder nur Leerzeichen) sein kann.

Der Grund, den Sie nicht haben können, A a(())ist einfach, weil die Grammatik es nicht zulässt. Ich kann jedoch nicht antworten, warum die Leute, die C ++ erstellt haben, diese spezielle Verwendung von leeren Parens nicht als Sonderfall zugelassen haben - ich würde vermuten, dass sie einen solchen Sonderfall lieber nicht einführen würden, wenn es einen gäbe eine vernünftige Alternative.

Michael Burr
quelle