Cattura lambda e parametro con lo stesso nome: chi ombreggia l'altro? (clang vs 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 e versioni successive stampa "Stai usando clang ++!" e avvisa che la cattura foo non è stata utilizzata.

  • g ++ 4.9.0 e versioni successive stampa "Stai usando g ++!" e avvisa che il parametro foo non è stato utilizzato.

Quale compilatore sta seguendo più accuratamente lo standard C ++ qui?

esempio di wandbox


1
Incollare il codice da wandbox a qui (sembra che abbiano dimenticato il pulsante di condivisione) sembra che VS2015 (?) Sia d'accordo con clang dicendo l' avvertimento C4458: la dichiarazione di 'foo' nasconde un membro della classe .
nwp,

12
Grande esempio ...
Deviantfan

4
Lambda ha un tipo con un operatore di chiamata funzione modello, quindi la logica mi farebbe dire che il parametro dovrebbe ombreggiare la variabile catturata come se fosse dentro struct Lambda { template<typename T> void operator()(T foo) const { /* ... */ } private: decltype(outer_foo) foo{outer_foo}; }.
skypjack

2
@nwp VS è errato, i membri dei dati della lambda sono senza nome e quindi non possono essere ombreggiati. Lo standard dice che "l'accesso a un'entità acquisita viene trasformato in accesso al membro di dati corrispondente", il che ci lascia al punto di partenza.
n. 'pronomi' m.

10
Spero che la versione di clang sia corretta - sarebbe aprendo una nuova strada se qualcosa al di fuori di una funzione oscura il parametro della funzione, invece del contrario!
MM

Risposte:


65

Aggiornamento: come promesso dalla cattedra Core nella citazione in basso, il codice è ora mal formato :

Se un identificatore in una semplice cattura appare come il dichiaratore-id di un parametro della lambda-dichiaratore 's parametro-dichiarazione-clausola , il programma è mal-formata.


Qualche tempo fa c'erano alcuni problemi riguardanti la ricerca dei nomi in lambdas. Sono stati risolti da N2927 :

La nuova formulazione non si basa più sulla ricerca per rimappare gli usi delle entità acquisite. Nega più chiaramente le interpretazioni che un'istruzione composta lambda viene elaborata in due passaggi o che i nomi in tale istruzione composta potrebbero risolversi in un membro del tipo di chiusura.

La ricerca viene sempre eseguita nel contesto dell'espressione lambda , mai "dopo" la trasformazione nel corpo della funzione membro di un tipo di chiusura. Vedi [expr.prim.lambda] / 8 :

Il lambda-espressione s' composto-dichiarazione si ottiene la funzione di corpo ([dcl.fct.def]) dell'operatore funzione di chiamata, ma per scopi di ricerca del nome, [...], il composto-dichiarazione è considerata nel contesto di l' espressione lambda . [ Esempio :

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

- fine esempio ]

(L'esempio chiarisce inoltre che la ricerca non considera in qualche modo il membro di acquisizione generato del tipo di chiusura.)

Il nome foonon viene (ri) dichiarato nella cattura; è dichiarato nel blocco che racchiude l'espressione lambda. Il parametro fooè dichiarato in un blocco nidificato in quel blocco esterno (vedere [basic.scope.block] / 2 , che menziona esplicitamente anche i parametri lambda). L'ordine di ricerca è chiaramente dai blocchi interni a quelli esterni . Quindi il parametro dovrebbe essere selezionato, cioè Clang ha ragione.

Se dovessi rendere la cattura una init-cattura, ovvero foo = ""invece di foo, la risposta non sarebbe chiara. Questo perché l'acquisizione ora induce effettivamente una dichiarazione il cui "blocco" non viene dato. Ho mandato un messaggio alla sedia principale su questo, che ha risposto

Questo è il numero 2211 (un nuovo elenco di numeri apparirà sul sito open-std.org a breve, sfortunatamente con solo segnaposto per una serie di problemi, di cui questo è uno; sto lavorando sodo per colmare quelle lacune prima della Kona incontro alla fine del mese). CWG ne ha discusso durante la nostra teleconferenza di gennaio e la direzione è di rendere il programma mal formato se un nome di acquisizione è anche un nome di parametro.


Nulla per me fare a pezzi qui :) Una semplice acquisizione non dichiara nulla, quindi il risultato corretto della ricerca del nome è abbastanza ovvio (BTW, GCC ha ragione se usi un'acquisizione predefinita invece della cattura esplicita). gli init-capture sono un po 'più complicati.
TC

1
@TC sono d'accordo. Ho presentato un problema fondamentale, ma a quanto pare è già stato discusso, vedi la risposta modificata.
Columbo,

6

Sto cercando di mettere insieme alcuni commenti alla domanda per darti una risposta significativa.
Prima di tutto, nota che:

  • I membri di dati non statici vengono dichiarati per la lambda per ciascuna variabile acquisita per copia
  • Nel caso specifico, lambda ha un tipo di chiusura che ha un operatore di chiamata di funzione di modello inline pubblico che accetta un parametro denominato foo

Pertanto la logica mi farebbe dire a prima vista che il parametro dovrebbe oscurare la variabile acquisita come se in:

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

Ad ogni modo, @nm ha osservato correttamente che i membri di dati non statici dichiarati per le variabili acquisite con copia sono effettivamente senza nome. Detto questo, il membro di dati senza nome è ancora accessibile per mezzo di un identificatore (cioè foo). Pertanto, il nome del parametro dell'operatore di chiamata della funzione dovrebbe ancora (per così dire) ombreggiare quell'identificatore .
Come correttamente sottolineato da @nm nei commenti alla domanda:

l'entità acquisita originale [...] dovrebbe essere ombreggiata normalmente secondo le regole dell'ambito

Per questo motivo, direi che clang ha ragione.


Come spiegato sopra, la ricerca in questo contesto non viene mai eseguita come se fossimo nel tipo di chiusura trasformato.
Columbo,

@Columbo Sto aggiungendo una linea che mi mancava anche se era chiaro dal ragionamento, cioè che il clang ha ragione. La parte divertente è che ho trovato [expr.prim.lambda] / 8 mentre cercavo di dare una risposta, ma non sono stato in grado di usarlo correttamente come hai fatto tu. Ecco perché ogni volta è un piacere leggere le tue risposte. ;-)
skypjack
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.