Чому змінну const іноді не потрібно фіксувати в лямбда-звуці?


76

Розглянемо наступний приклад:

#include <cstdlib>

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

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

Чому мені потрібно захоплювати nв другій лямбді, а не mв першій лямбді? Я перевірив розділ 5.1.2 ( Лямбда-вирази ) стандарту C ++ 14, але мені не вдалося знайти причину. Чи можете ви вказати мені абзац, в якому це пояснюється?

Оновлення: Я спостерігав за такою поведінкою як GCC 6.3.1, так і 7 (транк). Clang 4.0 і 5 (транк) не працює з помилкою в обох випадках ( variable 'm' cannot be implicitly captured in a lambda with no capture-default specified).


2
constexprпротиconst
CinCout

1
clamg приймає перший, якщо ви перейдете m;наm + 0;
MM

4
Ця ж причина, чому std::array<int,m>все в порядку, а ні std::array<int,n>- ні.
п. 'займенники' m.

3
Я хотів би, щоб змінних не було, constexprякщо ви прямо не вказали це. Якщо хтось хоче constexprзмінну, тоді слід оголосити її як одну.
xinaiz

2
@BlackMoses: mне constexpr, це постійний інтегральний вираз під час компіляції ... що було задовго до того, як constexprбуло вигадано ключове слово.
Ben Voigt

Відповіді:


56

Для лямбди в області блоку - змінні, що відповідають певним критеріям в області охоплення можуть використовуватися обмежено в ламбда-середовищі, навіть якщо вони не захоплені.

Грубо кажучи, охоплення сфери охоплення включає будь-яку змінну, локальну для функції, що містить лямбда-випромінювання, яка була б у зоні дії в момент визначення лямбда-сигналу. Отже, це включає mі nнаведені вище приклади.

"Певні критерії" та "обмежені шляхи" конкретно (станом на C ++ 14):

  • Всередині лямбда змінна не повинна використовуватися з підтримкою , що означає, що вона не повинна піддаватися будь-якій операції, крім:
    • відображається як вираз із відкинутими значеннями ( m;є одним із них), або
    • з отриманням його значення.
  • Змінна повинна бути:
    • Число const, яке не є volatileцілим чи числом, чий ініціалізатор був константним виразом , або
    • A constexpr, не volatileзмінний (або його суб-об’єкт)

Посилання на C ++ 14: [expr.const] /2.7, [basic.def.odr] / 3 (перше речення), [expr.prim.lambda] / 12, [expr.prim.lambda] / 10.

Обгрунтування цих правил, як пропонують інші коментарі / відповіді, полягає в тому, що компілятор повинен мати можливість "синтезувати" лямбду без захоплення як вільну функцію, незалежну від блоку (оскільки такі речі можуть бути перетворені на покажчик). функціонувати); він може це зробити, незважаючи на посилання на змінну, якщо знає, що змінна завжди матиме одне і те ж значення, або може повторити процедуру отримання значення змінної незалежно від контексту. Але він не може цього зробити, якщо змінна час від часу може відрізнятися, або якщо адреса змінної потрібна, наприклад.


У вашому коді nбуло ініціалізовано несталий вираз. Тому nне можна використовувати в лямбді без захоплення.

mбув ініціалізований константним виразом 42, тому він відповідає "певним критеріям". Вираз із відкинутими значеннями не використовує вираз, тому m;його можна використовувати без mзахоплення. gcc є правильним.


Я б сказав, що різниця між двома компіляторами полягає в тому, що m;clang вважає odr-use m, а gcc - ні. Перше речення [basic.def.odr] / 3 досить складне:

Змінна x, ім'я якої відображається як потенційно обчислюваний вираз ex, підтримується , exякщо не застосовувати перетворення lvalue-to-rvalue для xотримання постійного виразу, який не викликає ніяких нетривіальних функцій і, якщо xє об'єктом, exє елементом набір потенційних результатів виразу e, де або застосовується перетворення lvalue-to-rvalue e, або eє виразом відкинутих значень.

але при уважному прочитанні він конкретно згадує, що вираз з відкинутими значеннями не використовує вираз.

Версія [basic.def.odr] на C ++ 11 спочатку не включала випадки виразу відкинутих значень, тому поведінка clang була б правильною за опублікованою C ++ 11. Однак текст, що з'являється в C ++ 14, був прийнятий як дефект проти C ++ 11 ( випуск 712 ), тому компілятори повинні оновити свою поведінку навіть у режимі C ++ 11.


Я думаю, що [expr] / 11 означає, що перетворення lvalue-to-rvalue в цьому випадку не застосовується.
cpplearner

@cpplearner Я не бачив цього раніше, але я згоден з вами зараз, коли ви на це вказали, дякую ... оновлю мою відповідь
MM

Знову читаючи, здається, чи m;виконує перетворення lvalue-to-rvalue неактуально. Визначення odr-use у [basic.def.odr] / 3 говорить: "або застосовується перетворення lvalue-to-rvalue e, або eвираз відкинутих значень", і тут вираз mє виразом відкинутих значень, тому врешті-решт змінна mне підтримується.
cpplearner

@cpplearner Роджер, я неправильно прочитав "вираз із відкинутими значеннями" як "неоцінений вираз"
М.М.,

34

Тому що це постійний вираз, компілятор трактує це так, ніби так і було [] { 42; }();

Правилом у [ expr.prim.lambda ] є:

Якщо лямбда-вираз або екземпляр шаблону оператора виклику функції загального лямбда-odr використовує (3.2) цю або змінну з автоматичною тривалістю зберігання від її досяжності, ця сутність повинна бути захоплена лямбда-виразом.

Ось цитата із стандарту [ basic.def.odr ]:

Змінна x, ім'я якої відображається як потенційно обчислюваний вираз ex, використовується, якщо застосовувати перетворення lvalue-to-rvalue до x не дає постійного виразу (...) або e не є виразом відкинутих значень.

(Вилучено не настільки важливу частину, щоб вона була короткою)

Моє просте розуміння таке: компілятор знає, що воно mє постійним під час компіляції, тоді як воно nбуде змінюватися під час виконання, і тому nйого потрібно захопити. nбуде використовуватися odr, оскільки вам потрібно поглянути на те, що знаходиться всередині, nпід час роботи. Іншими словами, nактуальним є той факт, що визначення "може бути лише одне" .

Це з коментаря М.М .:

m - це постійний вираз, оскільки це автоматична змінна const з ініціалізатором постійного виразу, але n не є постійним виразом, оскільки його ініціалізатор не був постійним виразом. Це висвітлено у [expr.const] /2.7. Згідно з першим реченням [basic.def.odr] / 3, константний вираз не використовується ODR

Дивіться тут демонстрацію .


5
Було б добре пояснити трохи детальніше, чому n; odr-використовує n , але m;не odr-use m
MM

1
Я не бачу, наскільки цей параграф релевантний, оскільки в ньому сказано, що сутність повинна бути захоплена лямбда-виразом. У першій лямбді немає захоплення, і все ж GCC компілює це.
s3rvac

@ s3rvac Див. коментар М.М. Ключ тут полягає в тому, що в одному випадку використовується odr, але в іншому не.
Ілля Попов

@IlyaPopov Змінні використовуються однаково в обох лямбдах, тому я не розумію, чому в одній лямбді, а не в іншій лямбді слід використовувати odr-використання.
s3rvac

2
@ s3rvac в абзаці сказано " Якщо [умови дотримані], ця організація буде захоплена". Умови виконуються, nале ні m.
MM

2

EDIT: Попередня версія моєї відповіді була неправильною. Початкова версія правильна, ось відповідна стандартна пропозиція:

[basic.def.odr]

  1. Змінна x, ім'я якої відображається як потенційно обчислюваний вираз ex, підтримується ex, якщо застосування перетворення lvalue-to-rvalue до x не дає постійного виразу , який не викликає нетривіальних функцій і, якщо x є об'єктом , ex є елементом набору потенційних результатів виразу e, де або перетворення lvalue-to-rvalue застосовується до e, або e є виразом відкинутих значень. ...

Оскільки mце постійний вираз, він не використовується з використанням і тому не потребує захоплення.

Здається, поведінка клангів не відповідає стандарту.


Перший насправді працює, спробуйте тут: ideone.com/o8WIO1
початківець

Я вважаю, що це нормально, див. Цитату зі стандарту. Яким ти це бачиш?
Новачок

@Beginner, що цитата насправді підтверджує мою відповідь. "повинен" накладає обмеження на програму. Це означає, що змінна повинна бути захоплена. Оскільки він не був захоплений (ні явно, ні неявно за допомогою стандартного захоплення), програма порушує це правило.
eerorika

Автоматичні змінні з охоплюючої області, які є постійними виразами, не потрібно фіксувати
MM

1
@MM, що цікаво. Чи можете ви знайти правило, яке так говорить?
eerorika
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.