Dlaczego zmienna const czasami nie musi być przechwycona w lambdzie?

Rozważ następujący przykład:

#include <cstdlib>

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

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

Dlaczego muszę przechwytywać n w drugiej lambdzie, a nie m w pierwszej lambdzie? Sprawdziłem sekcję 5.1.2 (wyrażenia Lambda) w standardzie C++14, ale nie mogłem znaleźć powodu. Czy możesz wskazać mi akapit, w którym jest to wyjaśnione?

Aktualizacja: obserwowałem to zachowanie zarówno z GCC 6.3.1 i 7 (trunk). Clang 4.0 i 5 (trunk) w obu przypadkach zawodzą z błędem (variable 'm' cannot be implicitly captured in a lambda with no capture-default specified).

Author: user2079303, 2017-04-18

3 answers

Dla lambda w zakresie bloku zmienne spełniające określone kryteria w zakresie osiągającym zakres mogą być używane w ograniczonym zakresie wewnątrz lambda, nawet jeśli nie są przechwytywane.

Z grubsza rzecz biorąc, reaching scope zawiera dowolną zmienną lokalną do funkcji zawierającej lambda, która byłaby w scope w punkcie, w którym została zdefiniowana lambda. Dotyczy to więc m i n w powyższych przykładach.

"pewne kryteria" i "ograniczone sposoby" są w szczególności (od C++14):

  • wewnątrz lambda zmienna nie może być odr-używana, co oznacza, że nie może być poddawana żadnej operacji poza:
    • pojawiające się jako wyrażenie odrzuconej wartości (m; jest jednym z nich), lub
    • Po pobraniu jego wartości.
  • zmienna musi być:
    • A const, NIE-volatile liczba całkowita lub enum, których inicjalizatorem było wyrażenie stałe , lub
    • a constexpr, nie-volatile zmienna (lub A sub-obiekt takich)

Odniesienia do C++14: [expr.const] / 2.7, [basic.def.odr] / 3 (pierwsze zdanie), [expr.prim.lambda] / 12, [expr.prim.lambda]/10.

Uzasadnienie tych reguł, jak sugerują inne komentarze/odpowiedzi, jest takie, że kompilator musi być w stanie "zsyntetyzować" lambda bez przechwytywania jako wolną funkcję niezależną od bloku (ponieważ takie rzeczy mogą być przekształcone na wskaźnik do funkcji); może to zrobić pomimo odwoływania się do zmiennej, jeśli jest ona wie, że zmienna zawsze będzie miała tę samą wartość, lub może powtórzyć procedurę uzyskania wartości zmiennej niezależnie od kontekstu. Ale nie może tego zrobić, jeśli zmienna może się różnić od czasu do czasu, lub jeśli adres zmiennej jest potrzebny na przykład.


W Twoim kodzie n zostało zainicjowane przez wyrażenie niestałe. Dlatego n nie może być użyty w lambda bez przechwycenia.

m została zainicjalizowana wyrażeniem stałym 42, spełnia więc "pewne kryteria". Wyrażenie odrzuconej wartości nie używa wyrażenia ODR, więc m; może być używane bez przechwytywania m. gcc ma rację.


Powiedziałbym, że różnica między tymi dwoma kompilatorami polega na tym, że clang uważa m; za ODR-use m, ale gcc nie. Pierwsze zdanie [basic.def.odr] / 3 jest dość skomplikowany:

Zmienna x, której nazwa pojawia się jako potencjalnie oceniane wyrażenie ex jest odr-użyty przez ex, chyba że zastosowanie konwersji lvalue-to-rvalue do x daje stałe wyrażenie, które nie wywołuje żadnych nietrywialnych funkcji i, jeśli x jest obiektem, ex jest elementem zbioru potencjalnych wyników wyrażenia e, gdzie albo konwersja lvalue-to-rvalue jest zastosowana do e, albo e jest wyrażeniem odrzuconej wartości.

Ale po dokładnym przeczytaniu wyraźnie wspomina, że wyrażenie odrzuconej wartości nie odr-użyj wyrażenia.

Wersja C++11 [basic.def.odr] pierwotnie nie zawierał przypadku wyrażenia odrzuconej wartości, więc zachowanie clanga byłoby poprawne pod opublikowanym C++11. Jednak tekst, który pojawia się w C++14 został zaakceptowany jako defekt wobec C++11 (Issue 712), więc Kompilatory powinny aktualizować swoje zachowanie nawet w trybie C++11.

 48
Author: M.M,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-18 11:00:23

Its ponieważ jest wyrażeniem stałym, kompilator traktuje to tak, jakby było [] { 42; }();

Reguła w [expr.prim.lambda ] to:

Jeśli wyrażenie lambda lub instancja wywołania funkcji szablon operatora ogólnego lambda ODR-używa (3.2) tego lub zmienna z automatycznym czasem przechowywania od jego zasięgu, podmiot ten jest ujmowany przez wyrażenie lambda.

Oto cytat ze standardu [podstawowe.def.odr]:

Zmienna x, której nazwa pojawia się jako potencjalnie oceniane wyrażenie ex is odr-used unless zastosowanie konwersji lvalue-to-rvalue do x daje wyrażenie stałe (...) lub e jest wyrażeniem odrzuconej wartości.

(usunięto nie tak ważną część, aby była krótka)

Moje proste zrozumienie jest takie: kompilator wie, że m jest stała w czasie kompilacji, podczas gdy n zmieni się w czasie wykonywania i dlatego n musi być schwytany. n będzie używany odr, ponieważ musisz spojrzeć na to, co jest w środku n w czasie uruchomienia. Innymi słowy fakt, że" może być tylko jedna " definicja n jest istotna.

To z komentarza M. M:

M jest wyrażeniem stałym, ponieważ jest zmienną automatyczną const z inicjalizatorem wyrażenia stałego, ale n nie jest stałą wyrażenie, ponieważ jego inicjalizator nie był wyrażeniem stałym. To jest objęty [expr.const] / 2.7. Wyrażenie stałe nie jest ODR-używany, zgodnie z pierwszym zdaniem [basic.def.odr] / 3

Zobacz tutajdemo .

 32
Author: Beginner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-18 11:06:41

EDIT: poprzednia wersja mojej odpowiedzi była błędna. Początkujący jest poprawny, tutaj jest odpowiedni standardowy cytat:

[podstawowe.def.odr]

  1. zmienna x, której nazwa pojawia się jako potencjalnie oceniane wyrażenie ex jest odr-używany przez ex, chyba że zastosowanie lvalue-to-rvalue konwersja do X daje stałe wyrażenie, które nie wywołuje żadnych nietrywialnych funkcji i, jeśli x jest obiektem, ex jest elementem zbioru potencjału. wyniki wyrażenia e, w którym albo Konwersja wartości lvalue-to-rvalue jest zastosowana do e, albo e jest wyrażeniem odrzuconej wartości. ...

Ponieważ m jest wyrażeniem stałym, nie jest używane odr i dlatego nie musi być przechwytywane.

Wygląda na to, że zachowanie klangów nie jest zgodne ze standardem.

 2
Author: user2079303,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-18 09:27:55