Przechwytywanie lambda i parametr o tej samej nazwie - kto przesłania drugi? (clang przeciwko 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 i nowsze wydrukuj "Używasz clang ++!" i ostrzegaj o niewykorzystaniu przechwytywania foo .

  • g ++ 4.9.0 i nowsze wydrukuj "Używasz g ++!" i ostrzec o nieużywanym parametrze foo .

Który kompilator dokładniej postępuje zgodnie ze standardem C ++?

przykład skrzynki różdżkowej

Vittorio Romeo
źródło
1
Wklejenie kodu z wandbox do tego miejsca (wydaje się, że zapomnieli przycisku udostępniania) sprawia, że ​​wygląda na to, że VS2015 (?) Zgadza się z ostrzeżeniem Clang, mówiąc ostrzeżenie C4458: deklaracja „foo” ukrywa członka klasy .
nwp
12
Świetny przykład ...
deviantfan
4
Lambda ma typ z operatorem wywołania funkcji szablonu, więc logika sprawiłaby, że powiedziałbym, że parametr powinien przesłaniać przechwyconą zmienną tak, jakby była w struct Lambda { template<typename T> void operator()(T foo) const { /* ... */ } private: decltype(outer_foo) foo{outer_foo}; }.
skypjack,
2
@nwp VS jest błędny, elementy składowe danych lambda są nienazwane i dlatego nie mogą być zasłonięte. Standard mówi, że „dostęp do przechwyconej encji jest przekształcany w dostęp do odpowiedniego elementu danych”, co pozostawia nas w kwadracie.
n. zaimki m.
10
Mam nadzieję, że wersja clang jest poprawna - byłoby przełomem, gdyby coś poza funkcją przesłaniało parametr funkcji, zamiast na odwrót!
MM,

Odpowiedzi:

65

Aktualizacja: zgodnie z obietnicą krzesła Core w dolnym cytacie, kod to teraz źle sformułowany :

Jeżeli identyfikator w prosty wychwytywania występuje jako declarator-id parametru z lambda-declarator „S -parametrów zgłoszenia klauzula program jest słabo formowane.


Jakiś czas temu było kilka problemów dotyczących wyszukiwania nazw w lambdach. Zostały rozwiązane przez N2927 :

Nowe sformułowanie nie polega już na wyszukiwaniu w celu ponownego odwzorowania użycia przechwyconych jednostek. Bardziej wyraźnie zaprzecza interpretacjom, że instrukcja złożona lambda jest przetwarzana w dwóch przebiegach, lub że jakiekolwiek nazwy w tej instrukcji złożonej mogą być rozpoznawane jako element członkowski typu zamknięcia.

Wyszukiwanie jest zawsze wykonywane w kontekście wyrażenia lambda , nigdy „po” transformacji do treści funkcji składowej typu zamknięcia. Zobacz [expr.prim.lambda] / 8 :

W lambda ekspresji „s Związek instrukcja_select daje funkcyjnego korpus ([]) dcl.fct.def operatora wywołania funkcji, ale dla celów wyszukiwania nazw [...], sygnał związek instrukcja_select rozpatrywane są w kontekście lambda ekspresji . [ Przykład :

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*
    }; 
  }
};

- przykład końca ]

(Z przykładu jasno wynika również, że wyszukiwanie w jakiś sposób nie uwzględnia wygenerowanego elementu przechwytywania typu zamknięcia).

Imię foonie jest (ponownie) deklarowane podczas brania; jest zadeklarowana w bloku zawierającym wyrażenie lambda. Parametr foojest zadeklarowany w bloku, który jest zagnieżdżony w tym zewnętrznym bloku (zobacz [basic.scope.block] / 2 , który również wyraźnie wspomina o parametrach lambda). Kolejność wyszukiwania jest wyraźnie od bloków wewnętrznych do zewnętrznych . Stąd parametr powinien być wybrany, czyli Clang ma rację.

Gdybyś miał zrobić przechwycenie jako przechwytywanie początkowe, tj. foo = ""Zamiast foo, odpowiedź nie byłaby jasna. Dzieje się tak, ponieważ przechwytywanie w rzeczywistości wywołuje teraz deklarację, której „blokada” nie została podana. Wysłałem wiadomość do głównego krzesła, który odpowiedział

To jest numer 2211 (nowa lista problemów pojawi się wkrótce na stronie open-std.org, niestety tylko z symbolami zastępczymi dla wielu problemów, z których jest jeden; ciężko pracuję, aby wypełnić te luki przed Kona spotkanie pod koniec miesiąca). CWG omawiało to podczas naszej styczniowej telekonferencji, a kierunek jest taki, aby program był źle sformułowany, jeśli nazwa przechwytywania jest również nazwą parametru.

Columbo
źródło
Nie mam tu nic do rozdzielenia :) Proste przechwycenie niczego nie deklaruje, więc poprawny wynik wyszukiwania nazwy jest dość oczywisty (BTW, GCC robi to dobrze, jeśli używasz domyślnego przechwytywania zamiast jawnego przechwytywania). przechwytywanie init jest nieco trudniejsze.
TC
1
@TC Zgadzam się. Złożyłem podstawowy problem, ale najwyraźniej zostało to już omówione, patrz zredagowana odpowiedź.
Columbo,
6

Próbuję zebrać kilka komentarzy do pytania, aby udzielić Ci znaczącej odpowiedzi.
Przede wszystkim zwróć uwagę, że:

  • Niestatyczne składowe danych są deklarowane dla lambda dla każdej zmiennej przechwytywanej przez kopiowanie
  • W konkretnym przypadku lambda ma typ zamknięcia, który ma publiczny operator wywołania funkcji szablonu wbudowanego akceptujący parametr o nazwie foo

Dlatego logika sprawiłaby, że na pierwszy rzut oka powiedziałbym, że parametr powinien przesłonić przechwyconą zmienną, jak gdyby:

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

W każdym razie @nm poprawnie zauważył, że niestatyczne elementy składowe danych zadeklarowane dla zmiennych przechwytywanych przez kopię są w rzeczywistości nienazwane. Mimo to dostęp do nienazwanego członka danych jest nadal możliwy za pomocą identyfikatora (to znaczy foo). Dlatego nazwa parametru operatora wywołania funkcji powinna nadal (pozwólcie mi powiedzieć) przesłonić ten identyfikator .
Jak słusznie zauważył @nm w komentarzach do pytania:

pierwotnie przechwycony podmiot [...] powinien być prześwietlony zgodnie z zasadami zakresu

Z tego powodu powiedziałbym, że brzęk jest właściwy.

skypjack
źródło
Jak wyjaśniono powyżej, wyszukiwanie w tym kontekście nigdy nie jest wykonywane tak, jakbyśmy byli w przekształconym typie zamknięcia.
Columbo,
@Columbo Dodam wiersz, którego przegapiłem, nawet jeśli wynikało to jasno z rozumowania, to znaczy, że brzęk ma rację. Zabawne jest to, że znalazłem [expr.prim.lambda] / 8, próbując udzielić odpowiedzi, ale nie byłem w stanie go poprawnie użyć, tak jak ty. Dlatego za każdym razem czytanie odpowiedzi jest przyjemnością. ;-)
skypjack