C ++ 11 Konstruktor Überladung Auflösung und initialiser_lists: Clang ++ und g ++ nicht einverstanden

8

Ich habe einen kleinen Teil von C ++ 11 Code, den g ++ (4.7 oder 4.8) nicht kompiliert und behauptet, dass der Aufruf des Konstruktors für B2 b2a (x, {P (y)}) mehrdeutig ist. Clang ++ ist mit diesem Code zufrieden, weigert sich aber, B2 b2b (x, {{P (y)}}) zu kompilieren, was g ++ perfekt zu kompilieren ist!

Beide Compiler sind mit dem B1-Konstruktor vollkommen zufrieden, entweder mit {...} oder {{}} als Argument. Kann jeder C ++ - Anwalt erklären, welcher Compiler korrekt ist (wenn) und was passiert? Code unten:

%Vor%

und die Compilerfehler, clang:

%Vor%

g ++:

%Vor%

Das riecht nach einer Wechselwirkung zwischen einheitlicher Initialisierung, Initialisiererlisten-Syntax und Funktionsüberlastung mit Vorlagen-Argumenten (von denen ich weiß, dass g ++ ziemlich streng ist), aber ich bin nicht genug von einem Standard-Anwalt, um entpacken zu können das richtige Verhalten hier!

    
Phil Armstrong 17.07.2013, 16:01
quelle

1 Antwort

5

Erster Code, dann was ich denke sollte passieren. (Im Folgenden werde ich den ersten Parameter ignorieren, da wir nur an dem zweiten Parameter interessiert sind. Der erste Parameter ist immer eine exakte Übereinstimmung in Ihrem Beispiel). Bitte beachten Sie, dass die Regeln derzeit in der Spezifikation in Fluss sind, also würde ich nicht sagen, dass der eine oder andere Compiler einen Fehler hat.

%Vor%

Dieser Code kann den const Y& -Konstruktor in C ++ 11 nicht aufrufen, weil Y ein Aggregat ist und Y kein Datenelement vom Typ Y (natürlich) oder etwas anderes, das von ihm initialisiert werden kann ist etwas hässlich, und es wird daran gearbeitet, behoben zu werden - die C ++ 14 CD hat noch keinen Wortlaut dafür, also bin ich mir nicht sicher, ob finales C ++ 14 diesen Fix enthalten wird.

Der Konstruktor mit dem Parameter const A<Y>& kann aufgerufen werden - {y} wird als Argument für den Konstruktor von A<Y> verwendet und initialisiert den std::initializer_list<Y> des Konstruktors.

Daher - zweiter Konstruktor erfolgreich aufgerufen .

%Vor%

Hier zählt das im Grunde gleiche Argument für den Konstruktor mit dem Parameter const Y& .

Für den Konstruktor mit dem Parameter const A<Y>& ist es etwas komplizierter. Die Regel für die Konvertierungskosten in der Überladungsauflösung, die die Initialisierungskosten für std::initializer_list<T> berechnet, erfordert, dass jedes Element der versteiften Liste in T konvertiert werden kann. Allerdings haben wir vorher gesagt, dass {y} nicht in Y umgewandelt werden kann (da es ein Aggregat ist). Es ist jetzt wichtig zu wissen, ob std::initializer_list<T> ein Aggregat ist oder nicht. Ehrlich gesagt, ich habe keine Ahnung , ob es als ein Aggregat gemäß den Standardbibliotheksklauseln betrachtet werden muss oder nicht.

Wenn wir annehmen, dass es sich um ein Nicht-Aggregat handelt, dann würden wir den Kopierkonstruktor von std::initializer_list<Y> in Betracht ziehen, der wiederum genau die gleiche Sequenz von Tests auslöst (was zu einer "unendlichen Rekursion" bei der Überprüfung der Überladungsauflösung führt). . Da dies ziemlich seltsam und nicht implementierbar ist, glaube ich nicht, dass irgendeine Implementierung diesen Weg nimmt.

Wenn wir std::initializer_list zu einem Aggregat machen, sagen wir "nein, keine Konvertierung gefunden" (siehe das obige Aggregat-Problem). In diesem Fall, da wir den Initialisiererkonstruktor nicht mit der einzelnen Initialisiererliste als Ganzes aufrufen können, wird {{y}} in mehrere Argumente aufgeteilt, und die Konstruktoren von A<Y> nehmen diese einzeln. In diesem Fall würden wir also mit {y} einen std::initializer_list<Y> als einzigen Parameter initialisieren - was vollkommen in Ordnung ist und wie ein Zauber wirkt.

So unter der Annahme, dass std::initializer_list<T> ein Aggregat ist, ist das in Ordnung und rufen Sie den zweiten Konstruktor erfolgreich auf.

%Vor%

In diesem Fall und im nächsten Fall haben wir das Aggregationsproblem nicht mehr wie oben mit Y , da P<Y> einen vom Benutzer bereitgestellten Konstruktor hat.

Für den Konstruktor P<Y> wird dieser Parameter von {P<Y> object} initialisiert. Da P<Y> keine Initialisierungslisten hat, wird die Liste in einzelne Argumente aufgeteilt und der move-Konstruktor von P<Y> mit einem rvalue-Objekt von P<Y> aufgerufen.

Für den A<P<Y>> -Parameterkonstruktor ist es dasselbe wie im obigen Fall mit A<Y> , initialisiert von {y} : Da std::initializer_list<P<Y>> durch {P<Y> object} initialisiert werden kann, wird die Argumentliste nicht geteilt Die geschweiften Klammern werden verwendet, um den Konstruktor ' std::initializer_list<T> ' zu initialisieren.

Jetzt funktionieren beide Konstruktoren gut. Sie verhalten sich hier wie überladene Funktionen, und ihr zweiter Parameter erfordert in beiden Fällen eine benutzerdefinierte Konvertierung. Benutzerdefinierte Konvertierungssequenzen können nur verglichen werden, wenn in beiden Fällen die gleiche Konvertierungsfunktion oder Konstruktor verwendet wird - nicht der Fall. Daher ist dies in C ++ 11 (und in der C ++ 14 CD) mehrdeutig.

Beachten Sie, dass wir hier einen subtilen Punkt zum Erkunden haben

%Vor%

Dieses intuitive Zählergebnis wird wahrscheinlich im selben Lauf behoben werden, in dem die oben erwähnte Aggregat-Initialisierungs-Seltsamkeit behoben wird (beide werden das erste f nennen). Dies wird dadurch implementiert, dass {x}->X zu einer Identitätskonvertierung wird (wie auch X->x ). Derzeit ist es eine benutzerdefinierte Konvertierung.

Also, Mehrdeutigkeit hier .

%Vor%

Für den Konstruktor mit dem Parameter const P<Y>& teilen wir die Argumente erneut auf und holen das {P<Y> object} Argument an die Konstruktoren von P<Y> . Denken Sie daran, dass P<Y> einen Kopierkonstruktor hat. Aber die Komplikation ist, dass wir sie nicht verwenden dürfen (siehe 13.3.3.1p4), da dies eine benutzerdefinierte Konvertierung erfordern würde. Der einzige verbleibende Konstruktor ist der, der Y übernimmt, aber ein Y kann nicht von {P<Y> object} initialisiert werden.

Für den Konstruktor mit dem Parameter A<P<Y>> kann {{P<Y> object}} eine std::initializer_list<P<Y>> initialisieren, weil {P<Y> object} in P<Y> konvertierbar ist (anders als mit Y über - dang, aggregates).

Also zweiter Konstruktor erfolgreich aufgerufen .

Zusammenfassung für alle 4

  • zweiter Konstruktor erfolgreich aufgerufen
  • unter der Annahme, dass std::initializer_list<T> ein Aggregat ist, ist das in Ordnung und rufen Sie den zweiten Konstruktor erfolgreich
  • auf
  • Mehrdeutigkeit hier
  • zweiter Konstruktor erfolgreich aufgerufen
Johannes Schaub - litb 17.07.2013, 21:33
quelle