Captura e parâmetro Lambda com o mesmo nome - quem sombreia o outro? (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 uma impressão mais recente "Você está usando o clang ++!" e avise que a captura foo não está sendo usada.

  • g ++ 4.9.0 e mais recente, imprima "Você está usando g ++!" e avise que o parâmetro foo não está sendo usado.

Qual compilador está seguindo com mais precisão o padrão C ++ aqui?

exemplo wandbox

Vittorio Romeo
fonte
1
Colar o código da wandbox para aqui (eles parecem ter esquecido o botão de compartilhamento) faz parecer que o VS2015 (?) Concorda com o clang dizendo o aviso C4458: a declaração de 'foo' oculta o membro da classe .
NWP
12
Ótimo exemplo ..
deviantfan
4
O lambda tem um tipo com um operador de chamada de função de modelo, portanto, a lógica me faria dizer que o parâmetro deve sombrear a variável capturada como se estivesse struct Lambda { template<typename T> void operator()(T foo) const { /* ... */ } private: decltype(outer_foo) foo{outer_foo}; }.
Skypjack #
2
@nwp VS está errado, os membros de dados do lambda não têm nome e, portanto, não podem ser sombreados. O padrão diz que "o acesso a uma entidade capturada é transformado para acessar o membro de dados correspondente", o que nos deixa no quadrado.
n. 'pronomes' m.
10
Eu espero que a versão clang esteja correta - seria inovadora se algo fora de uma função sombrear o parâmetro function, em vez do contrário!
7777 MM

Respostas:

65

Atualização: conforme prometido pela cadeira Core na citação inferior, o código agora está mal formado :

Se um identificador em um simples de captura aparece como o declarator-id de um parâmetro do lambda-declarator 's parâmetro de declaração cláusula , o programa está mal-formado.


Houve alguns problemas relacionados à pesquisa de nomes em lambdas há um tempo atrás. Eles foram resolvidos pelo N2927 :

A nova redação não depende mais da pesquisa para remapear os usos das entidades capturadas. Nega mais claramente as interpretações de que a instrução composta de um lambda é processada em duas passagens ou que qualquer nome nessa instrução composta possa ser resolvido para um membro do tipo de fechamento.

A pesquisa é sempre feita no contexto da expressão lambda , nunca "depois" da transformação no corpo da função de membro de um tipo de fechamento. Veja [expr.prim.lambda] / 8 :

O lambda-expressão do composto-declaração produz a função de corpo ([dcl.fct.def]) do operador da chamada de função, mas para fins de pesquisa de nome, [...], o composto-declaração é considerado no contexto de a expressão lambda . [ Exemplo :

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

- exemplo final ]

(O exemplo também deixa claro que a pesquisa não considera de alguma forma o membro de captura gerado do tipo de fechamento.)

O nome foonão é (re) declarado na captura; é declarado no bloco que encerra a expressão lambda. O parâmetro fooé declarado em um bloco aninhado nesse bloco externo (consulte [basic.scope.block] / 2 , que também menciona explicitamente parâmetros lambda). A ordem da pesquisa é claramente do interior para o exterior . Portanto, o parâmetro deve ser selecionado, ou seja, Clang está certo.

Se você fizesse da captura uma captura init, ou seja, em foo = ""vez de foo, a resposta não seria clara. Isso ocorre porque a captura agora na verdade induz uma declaração cujo "bloco" não é fornecido. Enviei uma mensagem para a cadeira principal, que respondeu

Esta é a edição 2211 (uma nova lista de questões aparecerá em breve no site open-std.org, infelizmente com apenas espaços reservados para vários problemas, dos quais este é um; estou trabalhando duro para preencher essas lacunas antes do Kona reunião no final do mês). O CWG discutiu isso durante nossa teleconferência de janeiro, e a direção é tornar o programa mal formado se um nome de captura também for um nome de parâmetro.

Columbo
fonte
Não há nada para desmoronar aqui :) Uma captura simples não declara nada, portanto o resultado correto da pesquisa de nome é bastante óbvio (BTW, o GCC acerta se você usar uma captura-padrão em vez de explícita). init-capture s são um pouco mais complicados.
TC
1
@TC eu concordo. Arquivei um problema central, mas aparentemente isso já foi discutido, veja a resposta editada.
Columbo 8/17
6

Estou tentando juntar alguns comentários à pergunta para dar uma resposta significativa.
Antes de tudo, observe que:

  • Membros de dados não estáticos são declarados para o lambda para cada variável capturada por cópia
  • No caso específico, o lambda possui um tipo de fechamento que possui um operador público de chamada de função de modelo em linha que aceita um parâmetro chamado foo

Portanto, a lógica me faria dizer, à primeira vista, que o parâmetro deve sombrear a variável capturada como se estivesse em:

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

De qualquer forma, o @nm observou corretamente que os membros de dados não estáticos declarados para variáveis ​​capturadas por cópia são na verdade sem nome. Dito isto, o membro de dados sem nome ainda é acessado por meio de um identificador (ou seja foo). Portanto, o nome do parâmetro do operador de chamada de função ainda deve (digamos) sombrear esse identificador .
Como corretamente apontado por @nm nos comentários à pergunta:

a entidade capturada original [...] deve ser sombreada normalmente de acordo com as regras de escopo

Por causa disso, eu diria que o clang está certo.

skypjack
fonte
Como explicado acima, a pesquisa nesse contexto nunca é executada como se estivéssemos no tipo de fechamento transformado.
Columbo
@ Columbo Estou adicionando uma linha que eu perdi, mesmo que fosse clara a partir do raciocínio, que é o clang certo. A parte engraçada é que eu encontrei [expr.prim.lambda] / 8 enquanto tentava dar uma resposta, mas não consegui usá-la corretamente como você. É por isso que toda vez que é um prazer ler suas respostas. ;-) #
2178 skypjack