wake-up-neo.com

Warum wird dies nicht ohne einen Standardkonstruktor kompiliert?

Ich kann dies tun:

#include <iostream>

int counter;

int main()
{
    struct Boo
    {
        Boo(int num)
        {
            ++counter;
            if (Rand() % num < 7) Boo(8);
        }
    };

    Boo(8);

    return 0;
}

Dies wird gut kompilieren, mein Zählergebnis ist 21. Wenn ich jedoch versuche, das Boo -Objekt zu erstellen, das das Konstruktorargument anstelle eines Integer-Literales übergibt, wird ein Kompilierungsfehler angezeigt:

#include <iostream>

int counter;

int main()
{
    struct Boo
    {
        Boo(int num)
        {
            ++counter;
            if (Rand() % num < 7) Boo(num); // No default constructor 
                                            // exists for Boo
        }
    };

    Boo(8);

    return 0;
}

Wie heißt ein Standardkonstruktor im zweiten Beispiel, aber nicht im ersten? Dies ist ein Fehler, den ich in Visual Studio 2017 erhalte.

Auf dem Online C++ Compiler onlineGDB erhalte ich die Fehler:

error: no matching function for call to ‘main()::Boo::Boo()’
    if (Rand() % num < 7) Boo(num);

                           ^
note:   candidate expects 1 argument, 0 provided
71
Zebrafish

Clang gibt diese Warnmeldung aus:

<source>:12:16: warning: parentheses were disambiguated as redundant parentheses around declaration of variable named 'num' [-Wvexing-parse]
            Boo(num); // No default constructor 
               ^~~~~

Dies ist ein äußerst ärgerliches Analyseproblem. Da Boo der Name eines Klassentyps ist und num kein Typname ist, könnte Boo(num); entweder die Konstruktion einer temporären Datei vom Typ Boo mit sein num als Argument für den Konstruktor von Boo oder als Deklaration Boo num; mit zusätzlichen Klammern um den Deklarator num (welche Deklaratoren immer haben dürfen). Wenn beide gültige Interpretationen sind, verlangt der Standard, dass der Compiler eine Deklaration annimmt.

Wenn es als Deklaration analysiert wird, ruft Boo num; den Standardkonstruktor (den Konstruktor ohne Argumente) auf, der weder von Ihnen noch implizit deklariert wird (weil Sie einen anderen Konstruktor deklariert haben). Daher ist das Programm schlecht aufgebaut.

Dies ist bei Boo(8); kein Problem, da 8 kein Bezeichner einer Variablen sein kann (Deklarator-ID). Daher wird er als Aufruf analysiert, der ein temporäres Boo mit 8 als Argument für den Konstruktor erstellt. Dabei wird nicht der Standardkonstruktor aufgerufen (der nicht deklariert ist), sondern der manuell definierte.

Sie können dies von einer Deklaration unterscheiden, indem Sie entweder Boo{num}; anstelle von Boo(num); verwenden (da {} um den Deklarator nicht zulässig ist), indem Sie die temporäre Variable zu einer benannten Variablen machen, z. Boo temp(num); oder indem es als Operand in einen anderen Ausdruck gesetzt wird, z. (Boo(num));, (void)Boo(num); usw.

Beachten Sie, dass die Deklaration wohlgeformt wäre, wenn der Standardkonstruktor verwendet werden könnte, da sie sich im Zweigblockbereich der if und nicht im Blockbereich der Funktion befindet und einfach den num in den Funktionen schattiert Parameterliste.

In jedem Fall scheint es keine gute Idee zu sein, die temporäre Objekterstellung für etwas zu missbrauchen, das ein normaler (Member-) Funktionsaufruf sein sollte.

Diese besondere Art der ärgerlichsten Analyse mit einem einzelnen Namen ohne Typ in der Klammer kann nur auftreten, weil beabsichtigt ist, eine temporäre Datei zu erstellen und diese sofort zu verwerfen, oder alternativ, wenn beabsichtigt ist, eine temporäre Datei zu erstellen, die direkt als Initialisierung verwendet wird, z. Boo boo(Boo(num)); (deklariert tatsächlich, dass die Funktion boo einen Parameter namens num mit dem Typ Boo verwendet und Boo zurückgibt).

Das sofortige Verwerfen von Provisorien ist normalerweise nicht beabsichtigt und der Initialisierungsfall kann durch Verwendung von geschweiften Klammern oder doppelten Klammern (Boo boo{Boo(num)}, Boo boo(Boo{num}) oder Boo boo((Boo(num)));, aber nicht Boo boo(Boo((num)));).

Wenn Boo kein Typname war, konnte es sich nicht um eine Deklaration handeln, und es tritt kein Problem auf.

Ich möchte auch betonen, dass Boo(8); eine neue temporäre Datei vom Typ Boo erstellt, auch innerhalb des Klassenbereichs und der Konstruktordefinition. Es ist nicht, wie man irrtümlich annehmen könnte, ein Aufruf des Konstruktors mit dem Zeiger this des Aufrufers, wie dies bei normalen nicht statischen Elementfunktionen der Fall ist. Innerhalb des Konstruktorkörpers kann auf diese Weise kein anderer Konstruktor aufgerufen werden. Dies ist nur in der Elementinitialisierungsliste des Konstruktors möglich.


Dies geschieht, obwohl die Deklaration aufgrund eines fehlenden Konstruktors aufgrund von [stmt.ambig]/ falsch geformt wäre:

Die Disambiguierung ist rein syntaktisch; Das heißt, die Bedeutung der in einer solchen Anweisung vorkommenden Namen, unabhängig davon, ob es sich um Typnamen handelt oder nicht, wird in der Disambiguierung im Allgemeinen nicht verwendet oder durch sie geändert.

[...]

Die Disambiguierung geht dem Parsen voraus, und eine als Deklaration disambiguierte Aussage kann eine falsch geformte Deklaration sein.


Behoben in der Bearbeitung: Ich habe übersehen, dass die fragliche Deklaration in einem anderen Bereich als der Funktionsparameter liegt und die Deklaration daher wohlgeformt ist, wenn der Konstruktor verfügbar ist. Dies wird bei der Disambiguierung in keinem Fall berücksichtigt. Auch auf einige Details erweitert.

87
user10605163

Dies wird als ärgerlichste Syntaxanalyse (Der Begriff wurde von Scott Meyers verwendet in effektiver AWL) .

Boo(num) ruft weder den Konstruktor auf noch erstellt es eine temporäre Datei. Clang gibt eine gute Warnung zu sehen (auch mit dem richtigen Namen W lästiges Parsen ):

<source>:12:38: warning: parentheses were disambiguated as redundant parentheses around declaration of variable named 'num' [-Wvexing-parse]

Was der Compiler sieht, ist also äquivalent zu

Boo num;

das ist eine variable Dekleration. Sie haben eine Boo-Variable mit dem Namen num deklariert, die den Standardkonstruktor benötigt, obwohl Sie ein temporäres Boo-Objekt erstellen wollten. Nach dem c ++ - Standard muss der Compiler in Ihrem Fall davon ausgehen, dass es sich um eine Variablendeklaration handelt. Sie könnten jetzt sagen: "Hey, num ist ein int, tun Sie das nicht." Das Standard sagt :

Die Disambiguierung ist rein syntaktisch; Das heißt, die Bedeutung der in einer solchen Anweisung vorkommenden Namen, unabhängig davon, ob es sich um Typnamen handelt oder nicht, wird in der Disambiguierung im Allgemeinen nicht verwendet oder durch sie geändert. Klassenvorlagen werden nach Bedarf instanziiert, um festzustellen, ob es sich bei einem qualifizierten Namen um einen Typnamen handelt. Die Disambiguierung geht dem Parsen voraus, und eine als Deklaration disambiguierte Aussage kann eine falsch geformte Deklaration sein. Wenn während des Parsens ein Name in einem Vorlagenparameter anders gebunden wird als bei einer Testanalyse, ist das Programm fehlerhaft. Es ist keine Diagnose erforderlich. [Hinweis: Dies kann nur auftreten, wenn der Name zu einem früheren Zeitpunkt in der Deklaration deklariert wurde. - Endnote]

Es gibt also keinen Ausweg.

Für Boo(8) kann dies nicht passieren, da der Parser sicher sein kann, dass dies keine Deklaration ist (8 ist kein gültiger Bezeichnername) und den Konstruktor Boo(int) aufruft.

Übrigens: Sie können mit Klammern unterscheiden:

 if (Rand() % num < 7)  (Boo(num));

oder meiner meinung nach besser die neue einheitliche initialisierungssyntax verwenden

if (Rand() % num < 7)  Boo{num};

Welche kompilieren dann siehe hier und hier .

33
user32434999

Hier klingelt die Warnung

truct_init.cpp: 11: 11: Fehler: Neudefinition von 'num' mit einem anderen Typ: 'Boo' vs 'int'

1
sbh