Qual é o tipo de lambda quando deduzido com "auto" em C ++ 11?

141

Eu tinha uma percepção de que o tipo de lambda é um ponteiro de função. Quando realizei o seguinte teste, achei errado ( demo ).

#define LAMBDA [] (int i) -> long { return 0; }
int main ()
{
  long (*pFptr)(int) = LAMBDA;  // ok
  auto pAuto = LAMBDA;  // ok
  assert(typeid(pFptr) == typeid(pAuto));  // assertion fails !
}

O código acima está faltando algum ponto? Caso contrário, qual é a typeofexpressão a lambda quando deduzida com a autopalavra - chave?

iammilind
fonte
8
“O tipo de um lambda é um ponteiro de função” - isso seria ineficiente e perderia todo o ponto das lambdas.
Konrad Rudolph

Respostas:

144

O tipo de uma expressão lambda não é especificado.

Mas eles geralmente são mero açúcar sintático para functores. Um lambda é traduzido diretamente em um functor. Qualquer coisa dentro do []é transformada em parâmetros do construtor e membros do objeto functor, e os parâmetros dentro ()são transformados em parâmetros para o functor operator().

Um lambda que captura nenhuma variável (nada dentro []dos) pode ser convertido em um ponteiro de função (o MSVC2010 não suporta isso, se esse é o seu compilador, mas essa conversão faz parte do padrão).

Mas o tipo real do lambda não é um ponteiro de função. É algum tipo de functor não especificado.

jalf
fonte
1
O MSVC2010 não oferece suporte à conversão em ponteiro de função, mas o MSVC11. blogs.msdn.com/b/vcblog/archive/2011/09/12/10209291.aspx
KindDragon
17
+1 para "mero açúcar sintático para functores". Muita confusão potencial pode ser evitada lembrando-se disso.
Ben
4
um functor é nada com operator()basicamente stackoverflow.com/questions/356950/c-functors-and-their-uses
TankorSmash
107

É uma estrutura única e sem nome que sobrecarrega o operador de chamada de função. Toda instância de um lambda apresenta um novo tipo.

No caso especial de um lambda que não captura, a estrutura além disso tem uma conversão implícita em um ponteiro de função.

avakar
fonte
2
Boa resposta. Muito mais preciso que o meu. +1 :)
jalf
4
+1 na parte da unicidade, é muito surpreendente a princípio e merece atenção.
Matthieu M.
Não que isso realmente importe, mas o tipo é realmente sem nome ou apenas não recebe um nome até o momento da compilação? IOW, alguém poderia usar o RTTI para encontrar o nome que o compilador decidiu?
Ben
3
@ Ben, não tem nome e, no que diz respeito à linguagem C ++, não existe "um nome que o compilador decida". O resultado de type_info::name()é definido pela implementação, portanto, ele pode retornar qualquer coisa. Na prática, o compilador nomeará o tipo em nome do vinculador.
Avakar 31/10/11
1
Ultimamente, quando essa pergunta é feita, costumo dizer que o tipo de lambda tem um nome, o compilador sabe disso, é apenas indizível.
Andre Kostur 28/02
24

[C++11: 5.1.2/3]: O tipo da expressão lambda (que também é o tipo do objeto de fechamento) é um tipo de classe sem união não nomeado, sem nome - chamado de tipo de fechamento - cujas propriedades são descritas abaixo. Este tipo de classe não é um agregado (8.5.1). O tipo de fechamento é declarado no menor escopo de bloco, escopo de classe ou espaço de nome que contém a expressão lambda correspondente . [..]

A cláusula continua listando propriedades variadas desse tipo. Aqui estão alguns destaques:

[C++11: 5.1.2/5]:O tipo de fecho para um lambda-expressão tem um público inlineoperador chamada de função (13.5.4) cujos parâmetros e tipo de retorno estão descritos pelo lambda-expressão do parâmetro-declaração-cláusula e -retorno do tipo de fuga , respectivamente. [..]

[C++11: 5.1.2/6]:O tipo de fechamento para um lambda-expressão sem lambda-captura tem uma função pública não-virtual não explícita conversão const para ponteiro para função de ter os mesmos tipos de parâmetro e retorno como operador de chamada de função do tipo de fechamento. O valor retornado por essa função de conversão deve ser o endereço de uma função que, quando chamada, tem o mesmo efeito que a chamada do operador de chamada de função do tipo de fechamento.

A consequência desta passagem final é que, se você usasse uma conversão, seria capaz de atribuir LAMBDAa pFptr.

Raças de leveza em órbita
fonte
3
#include <iostream>
#include <typeinfo>

#define LAMBDA [] (int i)->long { return 0l; }
int main ()
{
  long (*pFptr)(int) = LAMBDA;  // ok
  auto pAuto = LAMBDA;  // ok

  std::cout<<typeid( *pAuto ).name() << std::endl;
  std::cout<<typeid( *pFptr ).name() << std::endl;

  std::cout<<typeid( pAuto ).name() << std::endl;
  std::cout<<typeid( pFptr ).name() << std::endl;
}

Os tipos de função são realmente os mesmos, mas o lambda apresenta um novo tipo (como um functor).

BЈовић
fonte
Eu recomendo a abordagem de desmembramento do CXXABI, se você já está seguindo esse caminho. Em vez disso, costumo usar __PRETTY_FUNCTION__, como dentro template<class T> const char* pretty(T && t) { return __PRETTY_FUNCTION__; }, e retirar o extra se ele começar a ficar lotado. Prefiro ver as etapas mostradas na substituição de modelo. Se estiver faltando __PRETTY_FUNCTION__, existem alternativas para o MSVC etc., mas os resultados sempre dependem do compilador pelo mesmo motivo que o CXXABI é necessário.
John P
1

Observe também que lambda é conversível em ponteiro de função. No entanto, typeid <> retorna um objeto não trvial que deve diferir de lambda para ponteiro de função genérica. Portanto, o teste para typeid <> não é uma suposição válida. Em geral, o C ++ 11 não quer que nos preocupemos com a especificação de tipo, tudo isso importa se um determinado tipo é conversível em um tipo de destino.

Syed Raihan
fonte
Isso é justo, mas os tipos de impressão ajudam bastante a chegar ao tipo correto, sem mencionar a captura dos casos em que o tipo é conversível, mas não satisfaz outras restrições. (Eu sempre empurrar em direção a "reificação" restrições sempre que possível, mas alguém tentar fazê-lo tem mais uma razão para mostrar o seu trabalho durante o desenvolvimento.)
John P
0

Uma solução prática de Como posso armazenar um objeto boost :: bind como um membro da classe? , tente boost::function<void(int)>ou std::function<void(int)>.

Gabriel
fonte
1
Entretanto, esteja ciente do custo de desempenho disso (uma chamada de função virtual ocorre sempre que a função é chamada).
precisa saber é o seguinte