Лямбда-захват и параметр с тем же именем - кто скрывает других? (лязг против gcc)
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++ здесь?
2 ответа
Обновление: как и было обещано председателем Core в нижней цитате, код теперь плохо сформирован:
Если идентификатор в простом захвате появляется как идентификатор объявления параметра предложения объявления лямбда-декларатора, то программа некорректна.
Было несколько проблем, связанных с поиском имен в лямбдах. Они были разрешены 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 обсуждала это во время нашей январской телеконференции, и направление состоит в том, чтобы сделать программу плохо сформированной, если имя записи также является именем параметра.
Я пытаюсь собрать несколько комментариев к вопросу, чтобы дать вам значимый ответ.
Прежде всего, обратите внимание, что:
- Нестатические члены данных объявляются для лямбды для каждой перехваченной копии переменной
- В конкретном случае лямбда имеет тип замыкания, в котором есть открытый оператор вызова встроенного шаблона, принимающий параметр с именем
foo
Поэтому логика заставит меня на первый взгляд сказать, что параметр должен скрывать захваченную переменную, как если бы:
struct Lambda {
template<typename T> void operator()(T foo) const { /* ... */ }
private: decltype(outer_foo) foo{outer_foo};
};
В любом случае, @nm правильно заметил, что нестатические члены-данные, объявленные для перехваченных копий переменных, на самом деле не называются. При этом к неназванному элементу данных по-прежнему осуществляется доступ с помощью идентификатора (то есть foo
). Следовательно, имя параметра оператора вызова функции должно (позвольте мне сказать) скрывать этот идентификатор.
Как правильно указано @nm в комментариях к вопросу:
исходный захваченный объект [...] должен быть обычно затенен в соответствии с правилами области видимости
Из-за этого я бы сказал, что лязг прав.