Lambda-Erfassung und gleichnamiger Parameter - wer beschattet den anderen? (clang vs gcc)

125
auto foo = "You're using g++!";
auto compiler_detector = [foo](auto foo) { std::puts(foo); };
compiler_detector("You're using clang++!");
  • clang ++ 3.6.0 und neuerer Ausdruck "Sie verwenden clang ++!" und warnen Sie davor, dass die Aufnahme foo nicht verwendet wird.

  • g ++ 4.9.0 und neuerer Ausdruck "Sie verwenden g ++!" und warnen, dass der Parameter foo nicht verwendet wird.

Welcher Compiler folgt hier genauer dem C ++ - Standard?

Zauberstabbox Beispiel

Vittorio Romeo
quelle
1
Das Einfügen des Codes von der Zauberstabbox hierher (sie scheinen den Freigabeknopf vergessen zu haben) lässt es so aussehen, als ob VS2015 (?) Mit dem Klirren übereinstimmt und die Warnung C4458 sagt: Die Deklaration von 'foo' verbirgt das Klassenmitglied .
nwp
12
Tolles Beispiel ..
Deviantfan
4
Das Lambda hat einen Typ mit einem Vorlagenfunktionsaufrufoperator, daher würde die Logik mich sagen lassen, dass der Parameter die erfasste Variable wie in schattieren sollte struct Lambda { template<typename T> void operator()(T foo) const { /* ... */ } private: decltype(outer_foo) foo{outer_foo}; }.
Skypjack
2
@nwp VS ist falsch, Datenelemente des Lambda sind unbenannt und können daher nicht beschattet werden. Der Standard besagt, dass "der Zugriff auf eine erfasste Entität in den Zugriff auf das entsprechende Datenelement umgewandelt wird", wodurch wir auf dem ersten Platz bleiben.
n. 'Pronomen' m.
10
Ich würde hoffen, dass die Clang-Version korrekt ist - es würde neue Wege beschreiten, wenn etwas außerhalb einer Funktion den Funktionsparameter beschattet, anstatt umgekehrt!
MM

Antworten:

65

Update: Wie vom Core Chair im unteren Zitat versprochen, ist der Code jetzt schlecht geformt :

Wenn eine Kennung in einer einfachen Erfassungs erscheint als declarator-ID eines Parameters des Lambda-declarator ‚s - Parameter-Deklaration-Klausel wird das Programm schlecht ausgebildet.


Vor einiger Zeit gab es einige Probleme bei der Namenssuche in Lambdas. Sie wurden von N2927 gelöst :

Der neue Wortlaut basiert nicht mehr auf der Suche, um die Verwendung erfasster Entitäten neu zuzuordnen. Es bestreitet deutlicher die Interpretationen, dass die zusammengesetzte Anweisung eines Lambdas in zwei Durchgängen verarbeitet wird oder dass alle Namen in dieser zusammengesetzten Anweisung in ein Mitglied des Schließungstyps aufgelöst werden könnten.

Die Suche erfolgt immer im Kontext des Lambda-Ausdrucks , niemals "nach" der Transformation in den Elementfunktionskörper eines Schließungstyps. Siehe [expr.prim.lambda] / 8 :

Die Lambda-Ausdruck ‚s - Verbindung-Anweisung liefert die Funktion Körper ([dcl.fct.def]) der Funktion Call - Betreiber, aber für die Zwecke der Namenssuche, [...], die Verbindung-Anweisung wird im Kontext betrachtet von der Lambda-Ausdruck . [ Beispiel :

struct S1 {
  int x, y;
  int operator()(int);
  void f() {
    [=]()->int {
      return operator()(this->x+y);  // equivalent to: S1::operator()(this->x+(*this).y)
                                     // and this has type S1*
    }; 
  }
};

- Beispiel beenden ]

(Das Beispiel macht auch deutlich, dass bei der Suche das generierte Erfassungselement des Schließungstyps nicht berücksichtigt wird.)

Der Name foowird in der Aufnahme nicht (neu) deklariert. Es wird in dem Block deklariert, der den Lambda-Ausdruck enthält. Der Parameter foowird in einem Block deklariert, der in diesem äußeren Block verschachtelt ist (siehe [basic.scope.block] / 2 , in dem auch Lambda-Parameter explizit erwähnt werden). Die Reihenfolge der Suche ist eindeutig von inneren zu äußeren Blöcken . Daher sollte der Parameter ausgewählt werden, dh Clang ist richtig.

Wenn Sie die Erfassung zu einer Init-Erfassung machen würden, dh foo = ""stattdessen foo, wäre die Antwort nicht klar. Dies liegt daran, dass die Erfassung jetzt tatsächlich eine Deklaration induziert, deren "Block" nicht angegeben ist. Ich habe den Hauptstuhl darüber informiert, der geantwortet hat

Dies ist Ausgabe 2211 (eine neue Problemliste wird in Kürze auf der Website open-std.org erscheinen, leider nur mit Platzhaltern für eine Reihe von Ausgaben, von denen dies eine ist. Ich arbeite hart daran, diese Lücken vor der Kona zu schließen Treffen am Ende des Monats). CWG hat dies während unserer Telefonkonferenz im Januar besprochen. Die Richtung besteht darin, das Programm schlecht zu gestalten, wenn ein Erfassungsname auch ein Parametername ist.

Columbo
quelle
Hier kann ich nichts auseinander reißen :) Ein einfaches Capture deklariert nichts, daher ist das korrekte Ergebnis der Namenssuche ziemlich offensichtlich (übrigens, GCC macht es richtig, wenn Sie einen Capture-Standard anstelle eines expliziten Captures verwenden). Init-Captures sind etwas kniffliger.
TC
1
@TC Ich stimme zu. Ich habe ein Kernproblem eingereicht, aber anscheinend wurde dies bereits besprochen, siehe die bearbeitete Antwort.
Columbo
6

Ich versuche, ein paar Kommentare zu der Frage zusammenzufassen, um Ihnen eine aussagekräftige Antwort zu geben.
Beachten Sie zunächst Folgendes:

  • Nicht statische Datenelemente werden für das Lambda für jede kopiererfasste Variable deklariert
  • In dem speziellen Fall hat das Lambda einen Schließungstyp, bei dem ein öffentlicher Funktionsaufrufoperator für Inline-Vorlagen einen Parameter mit dem Namen akzeptiert foo

Daher würde die Logik mich auf den ersten Blick sagen lassen, dass der Parameter die erfasste Variable wie folgt beschatten sollte:

struct Lambda {
    template<typename T> void operator()(T foo) const { /* ... */ }
    private: decltype(outer_foo) foo{outer_foo};
};

Wie auch immer, @nm hat richtig bemerkt, dass die nicht statischen Datenelemente, die für kopiererfasste Variablen deklariert wurden, tatsächlich unbenannt sind. Davon abgesehen wird auf das unbenannte Datenelement weiterhin über eine Kennung (dh foo) zugegriffen . Daher sollte der Parametername des Funktionsaufrufoperators diesen Bezeichner immer noch (sagen wir mal) schattieren .
Wie von @nm in den Kommentaren zur Frage richtig hervorgehoben:

Die ursprünglich erfasste Entität [...] sollte gemäß den Bereichsregeln normal beschattet werden

Aus diesem Grund würde ich sagen, dass das Klirren richtig ist.

Skypjack
quelle
Wie oben erläutert, wird die Suche in diesem Zusammenhang niemals so durchgeführt, als ob wir uns im transformierten Verschlusstyp befänden.
Columbo
@Columbo Ich füge eine Zeile hinzu, die ich verpasst habe, auch wenn aus den Überlegungen hervorgeht, dass das Klirren richtig ist. Der lustige Teil ist, dass ich [expr.prim.lambda] / 8 gefunden habe, als ich versucht habe, eine Antwort zu geben, aber ich konnte es nicht richtig verwenden, wie Sie es getan haben. Deshalb ist es jedes Mal eine Freude, Ihre Antworten zu lesen. ;-)
Skypjack