Warum muss eine const-Variable manchmal nicht in einem Lambda erfasst werden?

76

Betrachten Sie das folgende Beispiel:

#include <cstdlib>

int main() {
    const int m = 42;
    [] { m; }(); // OK

    const int n = std::rand();
    [] { n; }(); // error: 'n' is not captured
}

Warum muss ich nim zweiten Lambda erfassen, aber nicht mim ersten Lambda? Ich habe Abschnitt 5.1.2 ( Lambda-Ausdrücke ) im C ++ 14-Standard überprüft, aber keinen Grund gefunden. Können Sie mich auf einen Absatz verweisen, in dem dies erklärt wird?

Update: Ich habe dieses Verhalten sowohl bei GCC 6.3.1 als auch bei 7 (Trunk) beobachtet. Clang 4.0 und 5 (Trunk) schlagen in beiden Fällen mit einem Fehler fehl ( variable 'm' cannot be implicitly captured in a lambda with no capture-default specified).

s3rvac
quelle
2
constexprvsconst
CinCout
1
Clamg akzeptiert die erste, wenn Sie m;zum + 0;
MM
4
Gleicher Grund warum std::array<int,m>ist OK und std::array<int,n>nicht.
n. 'Pronomen' m.
3
Ich wünschte, Variablen wären es nicht, constexprwenn Sie es nicht explizit angeben. Wenn jemand eine constexprVariable will , sollte man sie als eine deklarieren.
Xinaiz
2
@BlackMoses: mnicht constexpr, es ist ein konstanter integraler Ausdruck zur Kompilierungszeit ... das war eine Sache, lange bevor das constexprSchlüsselwort erfunden wurde.
Ben Voigt

Antworten:

55

Für ein Lambda bei Block Umfang, Variablen bestimmte Kriterien erfüllen , in dem Erreichen Umfang kann in begrenzten Möglichkeiten innerhalb des Lambdas verwendet werden, auch wenn sie nicht erfaßt werden.

Grob gesagt umfasst das Erreichen des Gültigkeitsbereichs jede Variable, die lokal für die Funktion ist, die das Lambda enthält, und die sich zum Zeitpunkt der Definition des Lambdas im Gültigkeitsbereich befindet. Dies schließt also mund nin den obigen Beispielen ein.

Die "bestimmten Kriterien" und "eingeschränkten Möglichkeiten" sind spezifisch (ab C ++ 14):

  • Innerhalb des Lambda darf die Variable nicht odr-verwendet werden , was bedeutet, dass sie keiner Operation unterzogen werden darf, außer:
    • als Ausdruck für verworfene Werte erscheinen ( m;ist einer davon) oder
    • seinen Wert abrufen lassen.
  • Die Variable muss entweder sein:
    • A const, nicht volatileganzzahlig oder enum, dessen Initialisierer ein konstanter Ausdruck war , oder
    • A constexpr, nicht volatilevariabel (oder ein Unterobjekt davon)

Verweise auf C ++ 14: [expr.const] /2.7, [basic.def.odr] / 3 (erster Satz), [expr.prim.lambda] / 12, [expr.prim.lambda] / 10.

Der Grund für diese Regeln ist, wie in anderen Kommentaren / Antworten vorgeschlagen, dass der Compiler in der Lage sein muss, ein Lambda ohne Erfassung als freie Funktion unabhängig vom Block zu "synthetisieren" (da solche Dinge in einen Zeiger konvertiert werden können). Funktionieren); Dies kann trotz Verweis auf die Variable erfolgen, wenn bekannt ist, dass die Variable immer denselben Wert haben würde, oder es kann die Prozedur zum Abrufen des Variablenwerts unabhängig vom Kontext wiederholen. Dies ist jedoch nicht möglich, wenn sich die Variable von Zeit zu Zeit unterscheiden kann oder wenn beispielsweise die Adresse der Variablen benötigt wird.


Wurde in Ihrem Code ndurch einen nicht konstanten Ausdruck initialisiert. Daher nkann nicht in einem Lambda verwendet werden, ohne gefangen genommen zu werden.

mwurde durch einen konstanten Ausdruck initialisiert 42, damit er die "bestimmten Kriterien" erfüllt. Ein Ausdruck mit verworfenem Wert verwendet den Ausdruck nicht oder m;kann daher verwendet werden, ohne merfasst zu werden. gcc ist richtig.


Ich würde sagen, dass der Unterschied zwischen den beiden Compilern darin besteht, dass clang die Verwendung m;von odr in Betracht zieht m, gcc jedoch nicht. Der erste Satz von [basic.def.odr] / 3 ist ziemlich kompliziert:

Eine Variable , xderen Name als potentiell werteten Ausdrucks exist odr verwendet durch , exes sei denn , die L - Wert-zu-R - Wert Umwandlung in Anwendung xergibt einen konstanten Ausdruck, der nicht invoke Jede nicht-triviale Funktionen bietet und, wenn xein Objekt ist, exist ein Element von Die Menge der möglichen Ergebnisse eines Ausdrucks e, bei dem entweder die Umwandlung von lWert in rWert angewendet ewird oder eein Ausdruck mit verworfenem Wert ist.

Beim genauen Lesen wird jedoch ausdrücklich erwähnt, dass ein Ausdruck mit verworfenem Wert den Ausdruck nicht verwendet .

Die C ++ 11-Version von [basic.def.odr] enthielt ursprünglich nicht den Ausdruck für verworfene Werte, sodass das Verhalten von clang unter dem veröffentlichten C ++ 11 korrekt wäre. Der in C ++ 14 angezeigte Text wurde jedoch als Fehler in C ++ 11 ( Problem 712 ) akzeptiert , sodass Compiler ihr Verhalten auch im C ++ 11-Modus aktualisieren sollten.

MM
quelle
Ich denke, [Ausdruck] / 11 bedeutet, dass die Umwandlung von lWert in rWert in diesem Fall nicht angewendet wird.
cpplearner
@cpplearner Ich habe das vorher nicht gesehen, aber ich stimme Ihnen jetzt zu, da Sie darauf hingewiesen haben, danke ... wird meine Antwort aktualisieren
MM
Beim erneuten Lesen scheint m;es irrelevant zu sein, ob eine Konvertierung von Wert zu Wert durchgeführt wird. Die Definition von odr-use in [basic.def.odr] / 3 besagt, dass "entweder die Umwandlung von lWert in rWert angewendet ewird oder eein Ausdruck mit verworfenem Wert ist", und hier ist der Ausdruck mein Ausdruck mit verworfenem Wert. mAm Ende wird die Variable also nicht odr-verwendet.
cpplearner
@cpplearner Roger, ich habe "Ausdruck mit verworfenem Wert" als "nicht bewerteten Ausdruck" falsch verstanden
MM
34

Da es sich um einen konstanten Ausdruck handelt, behandelt der Compiler ihn so, als ob er es wäre [] { 42; }();

Die Regel in [ expr.prim.lambda ] lautet:

Wenn ein Lambda-Ausdruck oder eine Instanziierung der Funktionsaufruf-Operator-Vorlage eines generischen Lambda-ODR (3.2) diese oder eine Variable mit automatischer Speicherdauer ab ihrem Erreichen des Gültigkeitsbereichs verwendet, wird diese Entität vom Lambda-Ausdruck erfasst.

Hier ein Zitat aus dem Standard [ basic.def.odr ]:

Eine Variable x, deren Name als potenziell ausgewerteter Ausdruck ex erscheint, wird odr verwendet, es sei denn, die Anwendung der Umwandlung von lWert in rWert auf x ergibt einen konstanten Ausdruck (...) oder e ist ein Ausdruck mit verworfenem Wert.

(Nicht so wichtiger Teil entfernt, um es kurz zu halten)

Mein einfaches Verständnis ist: Der Compiler weiß, dass dies mzur Kompilierungszeit konstant ist, während nes sich zur Laufzeit ändert und daher nerfasst werden muss. nwäre odr-verwendet, weil man sich zur nLaufzeit tatsächlich ansehen muss, was drin ist . Mit anderen Worten ist die Tatsache nrelevant , dass "es nur eine geben kann" .

Dies ist aus einem Kommentar von MM:

m ist ein konstanter Ausdruck, da es sich um eine konstante automatische Variable mit einem Initialisierer für konstante Ausdrücke handelt, aber n ist kein konstanter Ausdruck, da sein Initialisierer kein konstanter Ausdruck war. Dies wird in [expr.const] /2.7 behandelt. Der konstante Ausdruck wird gemäß dem ersten Satz von [basic.def.odr] / 3 nicht ODR-verwendet

Hier finden Sie eine Demo .

Anfänger
quelle
5
Es wäre gut, etwas detaillierter zu erklären, warum n; odr-verwendet n , aber m;nicht odr-verwendet m
MM
1
Ich verstehe nicht, wie relevant dieser Absatz ist, da er besagt, dass die Entität durch den Lambda-Ausdruck erfasst werden soll. Im ersten Lambda gibt es keine Erfassung, und dennoch kompiliert GCC sie.
s3rvac
@ s3rvac Siehe Kommentar von MM Der Schlüssel hier ist, dass es in einem Fall eine odr-Verwendung gibt, in dem anderen jedoch nicht.
Ilya Popov
@IlyaPopov Die Variablen werden in beiden Lambdas auf die gleiche Weise verwendet, daher kann ich nicht verstehen, warum in einem Lambda und nicht im anderen Lambda odr-verwendet werden sollte.
s3rvac
2
@ s3rvac Der Absatz lautet " Wenn [Bedingungen erfüllt sind], wird diese Entität erfasst". Die Bedingungen sind erfüllt, naber nicht m.
MM
2

EDIT: Die vorherige Version meiner Antwort war falsch. Anfänger ist richtig, hier ist relevantes Standardzitat:

[basic.def.odr]

  1. Eine Variable x, deren Name als potenziell ausgewerteter Ausdruck ex erscheint, wird von ex odr-verwendet, es sei denn, die Anwendung der Umwandlung von lWert in rWert auf x ergibt einen konstanten Ausdruck , der keine nicht trivialen Funktionen aufruft, und wenn x ein Objekt ist , ex ist ein Element der Menge möglicher Ergebnisse eines Ausdrucks e, wobei entweder die Umwandlung von lWert in rWert auf e angewendet wird oder e ein Ausdruck mit verworfenem Wert ist. ...

Schon seit m es sich um einen konstanten Ausdruck handelt, wird er nicht von odr verwendet und muss daher nicht erfasst werden.

Es scheint, dass das Clangs-Verhalten nicht dem Standard entspricht.

Eerorika
quelle
Der erste funktioniert tatsächlich, probieren Sie ihn hier aus: ideone.com/o8WIO1
Anfänger
Ich glaube das ist ok, siehe das Zitat aus dem Standard. Wie siehst du es?
Anfänger
@Beginner dieses Zitat bestätigt tatsächlich meine Antwort. "soll" das Programm einschränken. Dies bedeutet, dass die Variable erfasst werden muss. Da es nicht erfasst wurde (weder explizit noch implizit durch die Verwendung der Standarderfassung), verstößt das Programm gegen diese Regel.
Eerorika
Automatische Variablen aus dem erreichbaren Bereich, die konstante Ausdrücke sind, müssen nicht erfasst werden
MM
1
@ MM das ist interessant. Können Sie die Regel finden, die dies sagt?
Eerorika