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

Лямбда-захват и параметр с тем же именем - кто тенью другой? (clang vs 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 не используется.

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

пример wandbox

4b9b3361

Ответ 1

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

Если идентификатор в простом захвате появляется как идентификатор объявления параметра lambda-declarator parameter-declaration-clause, программа плохо сформирована.


Было несколько вопросов, касающихся поиска имени в lambdas некоторое время назад. Они были разрешены N2927:

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

Поиск выполняется всегда в контексте лямбда-выражения, никогда не "после" преобразования в тело функции члена типа закрытия. См. [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*
    }; 
  }
};

-end пример]

(В этом примере также ясно, что поиск не позволяет каким-либо образом рассмотреть созданный элемент захвата типа замыкания.)

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

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

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

Ответ 2

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

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

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

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

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

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

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