Warum kann ich diese Referenz ('& this') nicht in Lambda erfassen?

87

Ich verstehe die richtige Art zu erfassen this (Ändern von Objekteigenschaften) in einem Lambda wie folgt:

auto f = [this] () { /* ... */ };

Aber ich bin neugierig auf die folgende Besonderheit, die ich gesehen habe:

class C {
    public:
        void foo() {
            // auto f = [] () { // this not captured
            auto f = [&] () { // why does this work?
            // auto f = [&this] () { // Expected ',' before 'this'
            // auto f = [this] () { // works as expected
                x = 5;
            };
            f();
        }

    private:
        int x;
};

Die Kuriosität, die mich verwirrt (und die ich gerne beantwortet hätte), ist, warum Folgendes funktioniert:

auto f = [&] () { /* ... */ }; // capture everything by reference

Und warum ich nicht explizit thisdurch Bezugnahme erfassen kann :

auto f = [&this] () { /* ... */ }; // a compiler error as seen above.
Anthony Sottile
quelle
6
Warum willst du ? In Bezug auf Dinge, für die ein Verweis auf einen Zeiger möglicherweise jemals nützlich sein könnte: thisKann nicht geändert werden, ist nicht groß genug, um einen Verweis schneller zu machen ... und trotzdem existiert er nicht wirklich , also hat er Keine wirkliche Lebensdauer, was bedeutet, dass ein Hinweis darauf per Definition baumeln würde. thisist ein Wert, kein Wert.
underscore_d

Antworten:

109

Der Grund [&this]dafür ist, dass es sich um einen Syntaxfehler handelt. Jeder durch Kommas getrennte Parameter in lambda-introducerist ein capture:

capture:
    identifier
    & identifier
    this

Sie können sehen, dass &thisdies syntaktisch nicht zulässig ist. Der Grund, warum dies nicht zulässig ist, liegt darin, dass Sie niemals thisals Referenz erfassen möchten , da es sich um einen kleinen const-Zeiger handelt. Sie möchten es immer nur als Wert übergeben - daher unterstützt die Sprache die Erfassung einfach nichtthis Referenz .

Um thisexplizit zu erfassen , können Sie [this]als verwenden lambda-introducer.

Das erste capturekann ein sein, capture-defaultdas ist:

capture-default:
    &
    =

Dies bedeutet, dass automatisch erfasst wird, was auch immer ich verwende, nach Referenz ( &) bzw. nach Wert ( =) - die Behandlung von thisist jedoch etwas Besonderes - in beiden Fällen wird es aus den zuvor angegebenen Gründen nach Wert erfasst (auch bei einer Standarderfassung von)& , was normalerweise bedeutet Erfassung durch Referenz).

5.1.2.7/8:

Zum Zwecke der Namenssuche (3.4), Bestimmen des Typs und Werts von this(9.3.2) und Transformieren von ID-Ausdrücken, die sich auf nicht statische Klassenmitglieder beziehen, in Klassenausdrücke für den Zugriff von (*this)Klassenmitgliedern unter Verwendung von (9.3.1), der zusammengesetzten Anweisung [OF THE LAMBDA] wird im Kontext des Lambda-Ausdrucks betrachtet.

Das Lambda verhält sich also so, als ob es Teil der einschließenden Elementfunktion ist, wenn Elementnamen verwendet werden (wie in Ihrem Beispiel die Verwendung des Namens x), und generiert daher "implizite Verwendungen" thiswie bei einer Elementfunktion.

Wenn eine Lambda-Erfassung einen Erfassungsstandard enthält &, dürfen den Bezeichnern in der Lambda-Erfassung keine vorangestellten Bezeichner vorangestellt werden &. Wenn eine Lambda-Erfassung einen Erfassungsstandard enthält, darf =die Lambda-Erfassung nicht enthalten sein, thisund jeder darin enthaltenen Kennung muss vorangestellt werden &. Eine Kennung oder thisdarf in einer Lambda-Erfassung nicht mehr als einmal vorkommen.

So Sie verwenden können [this], [&], [=]oder [&,this]als lambda-introducerdas erfassen thisZeiger nach Wert.

Jedoch [&this]und [=, this]sind schlecht geformt. Im letzten Fall gcc warnt forgivingly für [=,this]die explicit by-copy capture of ‘this’ redundant with by-copy capture defaulteher als Fehler.

Andrew Tomazos
quelle
3
@KonradRudolph: Was ist, wenn Sie einige Dinge nach Wert und andere nach Referenz erfassen möchten? Oder möchten Sie mit dem, was Sie erfassen, sehr explizit sein?
Xeo
2
@KonradRudolph: Es ist eine Sicherheitsfunktion. Möglicherweise erfassen Sie versehentlich Namen, die Sie nicht beabsichtigen.
Andrew Tomazos
8
@KonradRudolph: Konstrukte auf Blockebene kopieren einen Zeiger auf die von ihnen verwendeten Objekte nicht auf magische Weise in einen neuen unsichtbaren anonymen Typ, der dann den umschließenden Bereich überleben kann - einfach durch Verwendung des Objektnamens in einem Ausdruck. Das Erfassen von Lambda ist viel gefährlicher.
Andrew Tomazos
5
@KonradRudolph Ich würde sagen "verwenden, [&]wenn Sie so etwas wie einen Block erstellen, der an eine Kontrollstruktur übergeben werden soll", aber explizit erfassen, wenn Sie ein Lambda produzieren, das für weniger einfache Zwecke verwendet werden soll. [&]ist eine schreckliche Idee, wenn das Lambda den gegenwärtigen Umfang überleben wird. Viele Verwendungen von Lambdas sind jedoch nur Möglichkeiten, Blöcke an Kontrollstrukturen zu übergeben, und der Block überlebt den Block, den er im Gültigkeitsbereich erstellt hat, nicht.
Yakk - Adam Nevraumont
2
@ Ruslan: Nein, thisist ein Schlüsselwort, thiskein Bezeichner.
Andrew Tomazos
6

Weil Standard nicht &thisin Captures-Listen hat:

N4713 8.4.5.2 Erfassungen:

lambda-capture:
    capture-default
    capture-list
    capture-default, capture-list

capture-default:
    &
    =
capture-list:
    capture...opt
    capture-list, capture...opt
capture:
    simple-capture
    init-capture
simple-capture:
    identifier
    &identifier
    this
    * this
init-capture:
    identifier initializer
    &identifier initializer
  1. Für die Zwecke der Lambda-Erfassung verweist ein Ausdruck möglicherweise wie folgt auf lokale Entitäten:

    7.3 A Dieser Ausdruck verweist möglicherweise auf * diesen.

Standard garantiert thisund *thisist gültig und &thisungültig. Erfassen thisbedeutet auch *this, dass das Objekt selbst als Referenz erfasst wird (was ein Wert ist, das Objekt selbst) , anstatt den thisZeiger nach Wert zu erfassen !

陳 力
quelle
*thiserfasst das Objekt nach Wert
sp2danny