Ответ 1
Для лямбды в области блока переменные, отвечающие определенным критериям в области охвата, могут использоваться ограниченным образом внутри лямбда, даже если они не захвачены.
Грубо говоря, область охвата включает любую переменную, локальную для функции, содержащую лямбда, которая была бы в области видимости в точке, определяемой лямбдой. Таким образом, это включает m
и n
в приведенных выше примерах.
"Определенные критерии" и "ограниченные способы" конкретно (как на С++ 14):
- Внутри лямбда переменная не должна использоваться как odr, что означает, что она не должна подвергаться какой-либо операции, за исключением:
- представляющий собой выражение с отброшенным значением (
m;
является одним из них) или - получив свое значение.
- представляющий собой выражение с отброшенным значением (
- Переменная должна быть:
- A
const
, nonvolatile
integer или enum, инициатор которого был константным выражением, или - A
constexpr
, nonvolatile
variable (или подобъект такого)
- A
Ссылки на С++ 14: [expr.const]/2.7, [basic.def.odr]/3 (первое предложение), [expr.prim.lambda]/12, [expr.prim.lambda]/10.
Обоснование этих правил, как это было предложено другими комментариями/ответами, заключается в том, что компилятор должен иметь возможность "синтезировать" лямбда без захвата как свободную функцию, независимую от блока (поскольку такие вещи могут быть преобразованы в указатель на функцию); он может это сделать, несмотря на обращение к переменной, если он знает, что переменная всегда будет иметь одно и то же значение или она может повторить процедуру получения значения переменной, не зависящей от контекста. Но это не может быть сделано, если переменная может время от времени отличаться или если требуется, например, адрес переменной.
В вашем коде n
был инициализирован не константным выражением. Поэтому n
нельзя использовать в лямбда без захвата.
m
был инициализирован константным выражением 42
, поэтому он соответствует "определенным критериям". Выражение отбрасываемого значения не использует выражение odr, поэтому m;
можно использовать без m
. gcc правильно.
Я бы сказал, что разница между двумя компиляторами заключается в том, что clang рассматривает m;
как odr-use m
, но gcc этого не делает. Первое предложение [basic.def.odr]/3 довольно сложно:
Переменная
x
, имя которой отображается как потенциально оцениваемое выражениеex
, является odr-используемымex
, если применение преобразования lvalue-to-rval вx
не дает выражения константы, которое не вызывает никаких нетривиальные функции, и еслиx
является объектом,ex
является элементом набора потенциальных результатов выраженияe
, где либо преобразование lvalue-to-rale применяется кe
, либоe
- выражение с отбрасыванием.
но при чтении внимательно упоминает, что выражение с отброшенным значением не использует odr-выражение.
Версия С++ 11 [basic.def.odr] изначально не включала случай выражения сбрасываемым значением, поэтому поведение clang было бы правильным в опубликованном С++ 11. Однако текст, который появляется в С++ 14, был принят как Дефект против С++ 11 (Проблема 712), поэтому компиляторы должны обновлять свое поведение даже в режиме С++ 11.