Dlaczego nie mogę uchwycić tego przez odniesienie („i to”) w lambdzie?

91

Rozumiem, że prawidłowy sposób przechwytywania this(modyfikowania właściwości obiektu) w lambdzie jest następujący:

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

Ale jestem ciekawy następującej osobliwości, którą zauważyłem:

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

Dziwność, przez którą jestem zdezorientowany (i chciałbym uzyskać odpowiedź), jest powodem, dla którego działa:

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

I dlaczego nie mogę wyraźnie uchwycić thisprzez odniesienie:

auto f = [&this] () { /* ... */ }; // a compiler error as seen above.
Anthony Sottile
źródło
6
Dlaczego miałbyś chcieć ? Jeśli chodzi o rzeczy do których odniesienie do wskaźnika może kiedykolwiek conceivably być użyteczne: thisnie może być zmieniony, nie jest wystarczająco duży, aby odniesienie szybciej ... i mimo to , że w rzeczywistości nie istnieje , a więc ma żadne prawdziwe życie, co oznacza, że ​​jakiekolwiek odniesienie do niego byłoby z definicji zwisające. thisjest prwartością, a nie lwartością.
underscore_d

Odpowiedzi:

111

Przyczyna [&this]nie działa, ponieważ jest to błąd składniowy. Każdy parametr oddzielony przecinkami w tagu lambda-introducerto capture:

capture:
    identifier
    & identifier
    this

Jak widać, &thisnie jest to dozwolone składniowo. Powodem, dla którego nie jest to dozwolone, jest to, że nigdy nie chciałbyś przechwytywać thisprzez odniesienie, ponieważ jest to mały wskaźnik do stałej. Zawsze chciałbyś przekazać to tylko według wartości - więc język po prostu nie obsługuje przechwytywania thisprzez odniesienie.

Aby przechwycić thisjawnie, możesz użyć [this]jako lambda-introducer.

Pierwszym capturemoże być a, capture-defaultktóry jest:

capture-default:
    &
    =

Oznacza to automatyczne przechwytywanie wszystkiego, czego używam, odpowiednio przez odniesienie ( &) lub wartość ( =) - jednak traktowanie thisjest specjalne - w obu przypadkach jest ujmowane według wartości z powodów podanych wcześniej (nawet przy domyślnym przechwytywaniu &, co zwykle oznacza przechwytywanie przez odniesienie).

5.1.2.7/8:

Na potrzeby wyszukiwania nazw (3.4), określenia typu i wartości this(9.3.2) oraz przekształcenia wyrażeń id odnoszących się do niestatycznych składowych klasy do wyrażeń dostępu do (*this)składowych klasy przy użyciu (9.3.1), instrukcja złożona [OF LAMBDA] rozpatruje się w kontekście wyrażenia lambda.

Zatem lambda zachowuje się tak, jakby była częścią otaczającej funkcji składowej, gdy używa się nazw składowych (tak jak w twoim przykładzie użycie nazwy x), więc wygeneruje „niejawne zastosowania”, thistak jak robi to funkcja składowa.

Jeśli przechwytywanie lambda zawiera domyślne ustawienie przechwytywania, to &identyfikatory w przechwytywaniu lambda nie powinny być poprzedzone znakiem &. Jeżeli przechwytywanie lambda obejmuje domyślne wychwytywanie, to znaczy =przechwytywanie lambda nie może zawierać, thisa każdy identyfikator, który zawiera, musi być poprzedzony znakiem &. Identyfikator lub thisnie powinien pojawiać się więcej niż jeden raz podczas przechwytywania lambda.

Więc można użyć [this], [&], [=]lub [&,this]jako lambda-introduceruchwycić thiswskaźnik przez wartość.

Jednak [&this]i [=, this]są źle ukształtowane. W tym ostatnim przypadku gcc forgivingly dla ostrzega [=,this], że explicit by-copy capture of ‘this’ redundant with by-copy capture defaultzamiast błędów.

Andrew Tomazos
źródło
3
@KonradRudolph: A co jeśli chcesz uchwycić niektóre rzeczy według wartości, a inne przez odniesienie? A może chcesz bardzo wyraźnie opisać to, co uchwycisz?
Xeo
2
@KonradRudolph: To funkcja bezpieczeństwa. Możesz przypadkowo przechwycić nazwy, których nie zamierzasz.
Andrew Tomazos
8
@KonradRudolph: Konstrukcje na poziomie bloku nie kopiują w magiczny sposób wskaźnika do obiektów, których używają, do nowego niewidzialnego typu anonimowego, który może następnie przetrwać obejmujący zakres - po prostu używając nazwy obiektu w wyrażeniu. Przechwytywanie lambda to o wiele bardziej niebezpieczna sprawa.
Andrew Tomazos
5
@KonradRudolph Powiedziałbym "użyj, [&]jeśli robisz coś takiego jak tworzenie bloku do przekazania do struktury kontrolnej", ale jawnie przechwytuj, jeśli tworzysz lambdę, która będzie używana do mniej prostych celów. [&]to okropny pomysł, jeśli lambda ma przetrwać obecny zakres. Jednak wiele zastosowań lambd to tylko sposoby przekazywania bloków do kontrolowania struktur, a blok nie będzie trwał dłużej niż blok, w którym został utworzony.
Yakk - Adam Nevraumont
2
@Ruslan: Nie, thisto słowo kluczowe, a thisnie identyfikator.
Andrew Tomazos,
6

Ponieważ standard nie ma &thisna listach przechwytywania:

N4713 8.4.5.2 Przechwytuje:

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. Na potrzeby przechwytywania lambda wyrażenie potencjalnie odwołuje się do jednostek lokalnych w następujący sposób:

    7.3 To wyrażenie potencjalnie odwołuje się do * this.

Tak, standardowe gwarancje thisi *thisjest ważny, a &thisjest nieprawidłowy. Ponadto przechwytywanie thisoznacza przechwytywanie *this(co jest wartością l, samego obiektu) przez odniesienie , a nie przechwytywanie thiswskaźnika według wartości !

陳 力
źródło
*thisprzechwytuje obiekt według wartości
sp2danny