Pourquoi ne puis-je pas capturer ceci par référence («& this») dans lambda?

91

Je comprends que la bonne façon de capturer this(pour modifier les propriétés d'un objet) dans un lambda est la suivante:

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

Mais je suis curieux de connaître la particularité suivante que j'ai vue:

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;
};

La bizarrerie par laquelle je suis confus (et j'aimerais avoir une réponse) est la raison pour laquelle ce qui suit fonctionne:

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

Et pourquoi je ne peux pas capturer explicitement thispar référence:

auto f = [&this] () { /* ... */ }; // a compiler error as seen above.
Anthony Sottile
la source
6
Pourquoi voudriez - vous voulez aller? En termes de choses pour lesquelles une référence à un pointeur pourrait être utile: thisne peut pas être modifiée, elle n'est pas assez grande pour faire une référence plus rapidement ... et de toute façon , elle n'existe pas , donc elle a pas de vie réelle, ce qui signifie que toute référence à celle-ci serait suspendue par définition. thisest une prvalue, pas une lvalue.
underscore_d

Réponses:

111

La raison [&this]ne fonctionne pas, c'est qu'il s'agit d'une erreur de syntaxe. Chaque paramètre séparé par des virgules dans le lambda-introducerest un capture:

capture:
    identifier
    & identifier
    this

Vous pouvez voir que la &thissyntaxe n'est pas autorisée. La raison pour laquelle il n'est pas autorisé est que vous ne voudriez jamais capturer thispar référence, car il s'agit d'un petit pointeur const. Vous ne voudrez jamais le transmettre que par valeur - donc le langage ne prend tout simplement pas en charge la capture thispar référence.

Pour capturer thisexplicitement, vous pouvez utiliser [this]comme lambda-introducer.

Le premier capturepeut être un capture-defaultqui est:

capture-default:
    &
    =

Cela signifie capturer automatiquement tout ce que j'utilise, par référence ( &) ou par valeur (= ) respectivement - cependant le traitement de thisest spécial - dans les deux cas, il est capturé par valeur pour les raisons données précédemment (même avec une capture par défaut de &, ce qui signifie généralement capture par référence).

5.1.2.7/8:

Aux fins de la recherche de nom (3.4), déterminer le type et la valeur de this (9.3.2) et de la transformation des expressions id faisant référence aux membres de classe non statiques en expressions d'accès aux membres de la classe en utilisant (*this)(9.3.1), l'instruction composée [OF THE LAMBDA] est considéré dans le contexte de l'expression lambda.

Ainsi, le lambda agit comme s'il faisait partie de la fonction membre englobante lors de l'utilisation de noms de membres (comme dans votre exemple l'utilisation du nom x ), donc il générera des «utilisations implicites» de la thismême manière qu'une fonction membre.

Si une capture lambda inclut une capture par défaut &, les identificateurs de la capture lambda ne doivent pas être précédés de &. Si une capture lambda inclut une capture par défaut qui est =, la capture lambda ne doit pas contenir thiset chaque identifiant qu'elle contient doit être précédé de& . Un identifiant outhis ne doit pas apparaître plus d'une fois dans une capture lambda.

Vous pouvez donc utiliser [this], [&], [=]ou [&,this]commelambda-introducer pour capturer le thispointeur en valeur.

Cependant [&this]et [=, this]sont mal formés. Dans le dernier cas, gcc avertit avec indulgence pour [=,this]cela explicit by-copy capture of ‘this’ redundant with by-copy capture defaultplutôt que pour des erreurs.

Andrew Tomazos
la source
3
@KonradRudolph: Et si vous voulez capturer certaines choses par valeur et d'autres par référence? Ou voulez-vous être très explicite avec ce que vous capturez?
Xeo
2
@KonradRudolph: C'est une fonction de sécurité. Vous pouvez accidentellement capturer des noms dont vous n'avez pas l'intention.
Andrew Tomazos
8
@KonradRudolph: Les constructions de niveau bloc ne copient pas comme par magie un pointeur vers les objets qu'elles utilisent dans un nouveau type anonyme invisible qui peut ensuite survivre à la portée englobante - simplement en utilisant le nom des objets dans une expression. La capture Lambda est bien plus une affaire dangereuse.
Andrew Tomazos
5
@KonradRudolph Je dirais "à utiliser [&]si vous faites quelque chose comme créer un bloc à passer dans une structure de contrôle", mais capturer explicitement si vous produisez un lambda qui sera utilisé à des fins moins simples. [&]est une idée horrible si le lambda va survivre à la portée actuelle. Cependant, de nombreuses utilisations des lambdas ne sont que des moyens de passer des blocs aux structures de contrôle, et le bloc ne survivra pas au bloc dans lequel il a été créé.
Yakk - Adam Nevraumont
2
@Ruslan: Non, thisc'est un mot-clé, ce thisn'est pas un identifiant.
Andrew Tomazos du
6

Parce que la norme n'a pas &thisdans les listes de captures:

N4713 8.4.5.2 Captures:

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. Pour les besoins de la capture lambda, une expression fait potentiellement référence aux entités locales comme suit:

    7.3 Cette expression fait potentiellement référence à * this.

Ainsi, la norme garantit thiset *thisest valide, et &thisest invalide. De plus, capturer thissignifie capturer *this(qui est une lvalue, l'objet lui-même) par référence , plutôt que capturer le thispointeur par valeur !

陳 力
la source
*thiscapture l'objet par valeur
sp2danny