Зйомка та параметр лямбда з такою ж назвою - хто відтіняє іншого? (кланг проти 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 ++ тут?

приклад wandbox


1
Вставляючи код з wandbox сюди (вони, здається, забули кнопку спільного доступу), здається, VS2015 (?) Погоджується з клангом, який каже попередження C4458: декларація "foo" приховує члена класу .
nwp

12
Чудовий приклад ..
девіантфан

4
Лямбда має тип з оператором виклику функції шаблону, таким чином, логіка змусить мене сказати, що параметр повинен затінювати захоплену змінну як би в struct Lambda { template<typename T> void operator()(T foo) const { /* ... */ } private: decltype(outer_foo) foo{outer_foo}; }.
skypjack

2
@nwp VS помиляється, члени лямбда даних не називаються, і тому їх не можна затінити. Стандарт говорить, що "доступ до захопленої сутності перетворюється на доступ до відповідного члена даних", який залишає нас у прямому.
н. 'займенники' м.

10
Я би сподівався, що версія clang є правильною - це би зламало нове місце, якщо щось поза функцією затінює параметр функції, а не навпаки!
ММ

Відповіді:


65

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

Якщо ідентифікатор в простому-захопленні з'являється як описатель-ідентифікатор параметра в лямбда-описувач «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 , де також явно згадуються параметри лямбда). Порядок пошуку чітко від внутрішнього до зовнішнього блоків . Звідси слід вибрати параметр, тобто Кланг має рацію.

Якби ви зробили захоплення init-capture, тобто foo = ""замість цього foo, відповідь була б не однозначною. Це тому, що захоплення зараз фактично викликає декларацію , "блок" якої не заданий. Я обміняв це питання основним кріслом, який відповів

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


Тут мені нічого не можна розірвати :) Просте захоплення нічого не заявляє, тому правильний результат пошуку імені є досить очевидним (BTW, GCC отримує це правильно, якщо ви використовуєте улаштування за замовчуванням замість явного захоплення). init-capture s дещо складніше.
ТК

1
@TC Я згоден. Я подав основне питання, але, мабуть, це вже обговорювалося, дивіться відредаговану відповідь.
Коламбо

6

Я намагаюся зібрати разом кілька коментарів до питання, щоб дати тобі змістовну відповідь.
Перш за все, зауважте, що:

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

Тому логіка змусить мене сказати з першого погляду, що параметр повинен затінювати захоплену змінну так, як ніби в:

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

У будь-якому випадку, @nm правильно зазначив, що нестатичні члени даних, оголошені для перехоплених на копію змінних, насправді безіменні. При цьому, неназваний член даних все ще отримує доступ за допомогою ідентифікатора (тобто foo). Тому ім'я параметра оператора функціонального виклику все одно має (дозвольте сказати) затінювати цей ідентифікатор .
Як правильно вказав @nm у коментарях до питання:

оригінальну захоплену сутність [...] слід затінювати нормально відповідно до правил застосування

Через це я б сказав, що кланг має рацію.


Як було пояснено вище, пошук у цьому контексті ніколи не виконується так, як ніби ми перебуваємо в трансформованому типі закриття.
Коламбо

@Columbo Я додаю рядок, який я пропустив, навіть якщо це було зрозуміло з міркувань, тобто кланг правильний. Найсмішніша частина полягає в тому, що я знайшов [expr.prim.lambda] / 8, намагаючись дати відповідь, але я не зміг правильно його використовувати, як ви. Ось чому кожного разу приємно читати свої відповіді. ;-)
skypjack
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.