Лямбда-захват и параметр с тем же именем - кто затеняет другого? (clang против gcc)

125
auto foo = "You're using g++!";
auto compiler_detector = [foo](auto foo) { std::puts(foo); };
compiler_detector("You're using clang++!");
  • clang ++ 3.6.0 и новее распечатайте "Вы используете clang ++!" и предупредить о неиспользовании захвата foo .

  • g ++ 4.9.0 и новее распечатайте «Вы используете g ++!» и предупредить о неиспользовании параметра foo .

Какой компилятор здесь более точно соответствует стандарту C ++?

пример палочки

Витторио Ромео
источник
1
При вставке кода из wandbox сюда (они, кажется, забыли кнопку «Поделиться») создается впечатление, что VS2015 (?) Согласен с clang, говорящим предупреждение C4458: объявление 'foo' скрывает член класса .
nwp 07
12
Отличный пример ..
deviantfan
4
Лямбда имеет тип с оператором вызова функции шаблона, поэтому логика заставит меня сказать, что параметр должен затенять захваченную переменную, как если бы в struct Lambda { template<typename T> void operator()(T foo) const { /* ... */ } private: decltype(outer_foo) foo{outer_foo}; }.
skypjack
2
@nwp VS неверен, члены данных лямбда не имеют имени и, следовательно, не могут быть затенены. В стандарте говорится, что «доступ к захваченной сущности преобразуется в доступ к соответствующему элементу данных», что оставляет нас на первом месте.
п. 'местоимения' м.
10
Я надеюсь, что версия clang верна - это будет прорывом, если что-то вне функции затеняет параметр функции, а не наоборот!
MM,

Ответы:

65

Обновление: как и обещал председатель ядра в нижней цитате, код теперь неправильно сформирован :

Если идентификатор в простом-захвате появляется как описатель-идентификатор параметра в лямбда-описателе «s параметра декларирование-п , программа плохо сформирована.


Некоторое время назад было несколько проблем с поиском имени в лямбдах. Их разрешил N2927 :

Новая формулировка больше не полагается на поиск для переназначения использования захваченных объектов. Он более четко отрицает интерпретацию того, что составной оператор лямбды обрабатывается за два прохода или что любые имена в этом составном операторе могут разрешаться в член типа замыкания.

Поиск всегда выполняется в контексте лямбда-выражения , а не «после» преобразования в тело функции-члена закрывающего типа. См. [Expr.prim.lambda] / 8 :

В лямбда-выражение «сек соединения-утверждение дает функцию-телу ([dcl.fct.def]) оператор вызова функции, но для целей поиска имен, [...], то соединение-заявление рассматривается в контексте лямбда-выражение . [ Пример :

struct S1 {
  int x, y;
  int operator()(int);
  void f() {
    [=]()->int {
      return operator()(this->x+y);  // equivalent to: S1::operator()(this->x+(*this).y)
                                     // and this has type S1*
    }; 
  }
};

- конечный пример ]

(Пример также проясняет, что поиск каким-то образом не учитывает сгенерированный член захвата типа закрытия.)

Имя fooне (повторно) объявлено в захвате; он объявлен в блоке, включающем лямбда-выражение. Параметр fooобъявлен в блоке, который вложен в этот внешний блок (см. [Basic.scope.block] / 2 , в котором также явно упоминаются лямбда-параметры). Порядок поиска явно от внутренних блоков к внешним . Значит параметр должен быть выбран, то есть Clang прав.

Если бы вы сделали захват init-capture, т.е. foo = ""вместо этого foo, ответ был бы неясным. Это потому, что захват теперь фактически вызывает объявление, чей «блок» не задан. Я написал об этом основному стулу, и он ответил

Это проблема 2211 (в ближайшее время на сайте open-std.org появится новый список проблем, к сожалению, с заполнителями только для ряда проблем, из которых это одна из них; я прилагаю все усилия, чтобы заполнить эти пробелы до того, как Kona встреча в конце месяца). CWG обсудила это во время нашей январской телеконференции, и нам нужно сделать программу некорректной, если имя захвата также является именем параметра.

Columbo
источник
Мне здесь нечего разорвать :) Простой захват ничего не объявляет, поэтому правильный результат поиска имени довольно очевиден (кстати, GCC правильно понимает, если вы используете захват по умолчанию вместо явного захвата). init-capture несколько сложнее.
TC,
1
@TC Согласен. Я подал основную проблему, но, видимо, это уже обсуждалось, см. Отредактированный ответ.
Коломбо
6

Я пытаюсь собрать несколько комментариев к вопросу, чтобы дать вам содержательный ответ.
Прежде всего, обратите внимание, что:

  • Члены нестатических данных объявляются для лямбда-выражения для каждой переменной, захваченной копией.
  • В конкретном случае лямбда имеет тип закрытия, который имеет общедоступный встроенный оператор вызова функции шаблона, принимающий параметр с именем foo

Следовательно, логика заставит меня с первого взгляда сказать, что параметр должен затенять захваченную переменную, как если бы в:

struct Lambda {
    template<typename T> void operator()(T foo) const { /* ... */ }
    private: decltype(outer_foo) foo{outer_foo};
};

В любом случае, @nm правильно заметил, что нестатические элементы данных, объявленные для переменных, захваченных копией, на самом деле не имеют имени. При этом доступ к безымянному элементу данных по-прежнему осуществляется с помощью идентификатора (то есть foo). Следовательно, имя параметра оператора вызова функции должно (позвольте мне сказать) затенять этот идентификатор .
Как правильно указал @nm в комментариях к вопросу:

исходный захваченный объект [...] должен обычно затеняться в соответствии с правилами области видимости

Из-за этого я бы сказал, что лязг - правильный.

skypjack
источник
Как объяснялось выше, поиск в этом контексте никогда не выполняется, как если бы мы были в преобразованном типе замыкания.
Коломбо
@Columbo Я добавляю строку, которую пропустил, даже если это было ясно из рассуждений, то есть clang - это правильно. Самое забавное, что я нашел [expr.prim.lambda] / 8, пытаясь дать ответ, но я не смог использовать его должным образом, как вы. Поэтому каждый раз приятно читать ваши ответы. ;-)
скайпджек 07