Eu já vi um caso em que o lambda era muito útil: um colega meu fazia código com milhões de iterações para resolver um problema de otimização de espaço. O algoritmo foi muito mais rápido ao usar um lambda do que uma função adequada! O compilador é o Visual C ++ 2013.
sergiol
Respostas:
1491
O problema
O C ++ inclui funções genéricas úteis como std::for_eache std::transform, que podem ser muito úteis. Infelizmente, eles também podem ser bastante complicados de usar, principalmente se o functor que você deseja aplicar for exclusivo para a função específica.
#include<algorithm>#include<vector>namespace{struct f {voidoperator()(int){// do something}};}void func(std::vector<int>& v){
f f;
std::for_each(v.begin(), v.end(), f);}
Se você usa apenas fuma vez e nesse local específico, parece exagero escrever uma classe inteira apenas para fazer algo trivial e único.
No C ++ 03, você pode tentar escrever algo como o seguinte, para manter o functor local:
void func2(std::vector<int>& v){struct{voidoperator()(int){// do something}} f;
std::for_each(v.begin(), v.end(), f);}
no entanto, isso não é permitido, fnão pode ser passado para uma função de modelo no C ++ 03.
A nova solução
O C ++ 11 introduz lambdas permite que você escreva um functor anônimo in-line para substituir o struct f. Para pequenos exemplos simples, isso pode ser mais fácil de ler (mantém tudo em um só lugar) e potencialmente mais simples de manter, por exemplo, da forma mais simples:
void func3(std::vector<int>& v){
std::for_each(v.begin(), v.end(),[](int){/* do something here*/});}
Funções Lambda são apenas açúcar sintático para functores anônimos.
Tipos de retorno
Em casos simples, o tipo de retorno do lambda é deduzido para você, por exemplo:
void func4(std::vector<double>& v){
std::transform(v.begin(), v.end(), v.begin(),[](double d){return d <0.00001?0: d;});}
no entanto, quando você começar a escrever lambdas mais complexas, encontrará rapidamente casos em que o tipo de retorno não pode ser deduzido pelo compilador, por exemplo:
Até agora, não usamos nada além do que foi passado para o lambda dentro dele, mas também podemos usar outras variáveis dentro do lambda. Se você deseja acessar outras variáveis, pode usar a cláusula capture (a []da expressão), que até agora não foi utilizada nesses exemplos, por exemplo:
Você pode capturar por referência e valor, que você pode especificar usando &e =respectivamente:
[&epsilon] captura por referência
[&] captura todas as variáveis usadas no lambda por referência
[=] captura todas as variáveis usadas no lambda por valor
[&, epsilon] captura variáveis como com [&], mas epsilon por valor
[=, &epsilon] captura variáveis como com [=], mas epsilon por referência
O gerado operator()é constpor padrão, com a implicação de captura constquando você os acessa por padrão. Isso tem o efeito de que cada chamada com a mesma entrada produziria o mesmo resultado, no entanto, você pode marcar o lambdamutable para solicitar que o operator()que é produzido não seja const.
@Yakk você foi preso. lambdas sem captura têm uma conversão implícita em ponteiros de tipo de função. a função de conversão é constsempre ...
Johannes Schaub - litb 31/03
2
@ JohannesSchaub-litb oh sorrateiro - e acontece quando você invoca ()- é passado como um lambda de argumento zero, mas como () constnão corresponde ao lambda, ele procura uma conversão de tipo que permita, que inclui conversão implícita -para-função-ponteiro, e chama assim! Sorrateiro!
Yakk - Adam Nevraumont
2
Interessante - originalmente eu pensava que lambdas eram funções anônimas e não functores, e estava confuso sobre como as capturas funcionavam.
precisa saber é o seguinte
50
Se você quiser usar lambdas como variáveis em seu programa, você pode usar: std::function<double(int, bool)> f = [](int a, bool b) -> double { ... }; Mas, geralmente, nós deixamos o compilador deduzir do tipo: auto f = [](int a, bool b) -> double { ... }; (e não se esqueça de #include <functional>)
Evert Heylen
11
Suponho que nem todo mundo entenda por que return d < 0.00001 ? 0 : d;é garantido o retorno duplo, quando um dos operandos é uma constante inteira (é por causa de uma regra implícita de promoção do operador?: Onde o segundo e o terceiro operando são balanceados entre si pela aritmética usual conversões, independentemente da escolha escolhida). Mudar para 0.0 : dtalvez tornasse o exemplo mais fácil de entender.
Lundin
831
O que é uma função lambda?
O conceito C ++ de uma função lambda se origina no cálculo lambda e na programação funcional. Um lambda é uma função sem nome que é útil (na programação real, não na teoria) para trechos curtos de código que são impossíveis de reutilizar e que não valem a pena nomear.
Em C ++, uma função lambda é definida assim
[](){}// barebone lambda
ou em toda a sua glória
[]()mutable-> T {}// T is the return type, still lacking throw()
[]é a lista de captura, ()a lista de argumentos e {}o corpo da função.
A lista de captura
A lista de captura define o que de fora do lambda deve estar disponível dentro do corpo da função e como. Pode ser:
um valor: [x]
uma referência [& x]
qualquer variável atualmente no escopo por referência [&]
igual a 3, mas pelo valor [=]
Você pode misturar qualquer um dos itens acima em uma lista separada por vírgulas [x, &y].
A lista de argumentos
A lista de argumentos é a mesma que em qualquer outra função C ++.
O corpo da função
O código que será executado quando o lambda for realmente chamado.
Dedução de tipo de retorno
Se um lambda tiver apenas uma instrução de retorno, o tipo de retorno poderá ser omitido e terá o tipo implícito de decltype(return_statement).
Mutável
Se um lambda é marcado como mutável (por exemplo []() mutable { }), é permitido alterar os valores que foram capturados por valor.
Casos de uso
A biblioteca definida pelo padrão ISO se beneficia muito das lambdas e aumenta a usabilidade em várias barras, pois agora os usuários não precisam desorganizar seu código com pequenos functores em algum escopo acessível.
C ++ 14
Em C ++ 14, lambdas foram estendidas por várias propostas.
Capturas Lambda inicializadas
Um elemento da lista de captura agora pode ser inicializado com =. Isso permite renomear variáveis e capturar movendo-se. Um exemplo retirado do padrão:
int x =4;auto y =[&r = x, x = x+1]()->int{
r +=2;return x+2;}();// Updates ::x to 6, and initializes y to 7.
e um retirado da Wikipedia mostrando como capturar com std::move:
auto ptr = std::make_unique<int>(10);// See below for std::make_uniqueauto lambda =[ptr = std::move(ptr)]{return*ptr;};
Lambdas genéricos
Agora, as lambdas podem ser genéricas ( autoseria equivalente a Taqui se houvesse
Tum argumento de modelo de tipo em algum lugar do escopo circundante):
auto lambda =[](auto x,auto y){return x + y;};
Dedução de tipo de retorno aprimorada
O C ++ 14 permite tipos de retorno deduzidos para todas as funções e não o restringe às funções do formulário return expression;. Isso também se estende às lambdas.
No seu exemplo para capturas lambda inicializadas acima, por que você encerra a função lamba com o () ;? Isso aparece como [] () {} (); ao invés de [](){};. Também não deveria o valor de x ser 5?
Ramakrishnan Kannan
7
@RamakrishnanKannan: 1) os () estão lá para chamar o lambda logo após defini-lo e fornecer a y seu valor de retorno. A variável y é um número inteiro, não a lambda. 2) Não, x = 5 é local para o lambda (uma captura por valor que, por acaso, tem o mesmo nome que a variável de escopo externo x) e, em seguida, x + 2 = 5 + 2 é retornado. A reatribuição da variável externa x acontece através da referência r:, r = &x; r += 2;mas isso acontece com o valor original de 4. #
The Vee
168
As expressões lambda são normalmente usadas para encapsular algoritmos para que possam ser passados para outra função. No entanto, é possível executar um lambda imediatamente após a definição :
Isso torna as expressões lambda uma ferramenta poderosa para refatorar funções complexas . Você começa envolvendo uma seção de código em uma função lambda, como mostrado acima. O processo de parametrização explícita pode ser realizado gradualmente com testes intermediários após cada etapa. Depois de ter o bloco de código totalmente parametrizado (como demonstrado pela remoção do &), você pode mover o código para um local externo e torná-lo uma função normal.
Da mesma forma, você pode usar expressões lambda para inicializar variáveis com base no resultado de um algoritmo ...
int a =[](int b ){int r=1;while(b>0) r*=b--;return r;}(5);// 5!
Como uma maneira de particionar a lógica do programa , você pode até achar útil passar uma expressão lambda como argumento para outra expressão lambda ...
As expressões lambda também permitem criar funções aninhadas nomeadas , que podem ser uma maneira conveniente de evitar lógica duplicada. Usar lambdas nomeadas também tende a ser um pouco mais fácil para os olhos (em comparação com lambdas anônimas inline) ao passar uma função não trivial como parâmetro para outra função. Nota: não esqueça o ponto-e-vírgula após a chave de fechamento.
auto algorithm =[&](double x,double m,double b )->double{return m*x+b;};int a=algorithm(1,2,3), b=algorithm(4,5,6);
Se a criação de perfil subsequente revelar uma sobrecarga significativa de inicialização para o objeto de função, você poderá reescrever isso como uma função normal.
Você percebeu que esta pergunta foi feita há 1,5 anos e que a última atividade foi há quase 1 ano? De qualquer forma, você está contribuindo com algumas idéias interessantes que nunca vi antes!
Piotr99
7
Obrigado pela dica simultânea de definir e executar! Eu acho que é importante notar que que funciona em como contidion para ifdeclarações: if ([i]{ for (char j : i) if (!isspace(j)) return false ; return true ; }()) // i is all whitespace, assumindo que ié umstd::string
Blacklight Brilhante
74
Assim, o seguinte é uma expressão jurídica: [](){}();.
#
8
Ugh! A (lambda: None)()sintaxe do Python é muito mais legível.
Dan04 30/05
9
@ Nobar - você está certo, eu digitei errado. Isso é legal (eu testei este tempo)main() {{{{((([](){{}}())));}}}}
Mark Lakata
38
Respostas
P: O que é uma expressão lambda no C ++ 11?
R: Sob o capô, é o objeto de uma classe gerada automaticamente com operador de sobrecarga () const . Esse objeto é chamado de fechamento e criado pelo compilador. Esse conceito de 'fechamento' está próximo do conceito de ligação do C ++ 11. Mas lambdas normalmente geram código melhor. E chamadas através de fechamentos permitem inlining completo.
P: Quando eu usaria um?
R: Para definir "lógica simples e pequena" e solicitar que o compilador execute a geração da pergunta anterior. Você fornece ao compilador algumas expressões que você deseja que esteja dentro do operador (). Todos os outros compiladores de coisas serão gerados para você.
P: Que tipo de problema eles resolvem que não era possível antes da introdução?
R: É algum tipo de açúcar de sintaxe, como sobrecarga de operadores, em vez de funções para operações personalizadas de adição e subrato ... Mas economiza mais linhas de código desnecessário para envolver 1-3 linhas de lógica real em algumas classes e etc.! Alguns engenheiros pensam que, se o número de linhas for menor, haverá menos chance de cometer erros (também acho)
Exemplo de uso
auto x =[=](int arg1){printf("%i", arg1);};void(*f)(int)= x;
f(1);
x(1);
Extras sobre lambdas, não cobertos por perguntas. Ignore esta seção se você não tiver interesse
1. Valores capturados. O que você pode capturar
1.1 Você pode fazer referência a uma variável com duração de armazenamento estático em lambdas. Todos eles são capturados.
1.2 Você pode usar o lambda para capturar valores "por valor". Nesse caso, os vars capturados serão copiados para o objeto de função (fechamento).
[captureVar1,captureVar2](int arg1){}
1.3 Você pode capturar ser referência. & - neste contexto significa referência, não ponteiros.
[&captureVar1,&captureVar2](int arg1){}
1.4 Existe notação para capturar todos os vars não estáticos por valor ou por referência
[=](int arg1){}// capture all not-static vars by value[&](int arg1){}// capture all not-static vars by reference
1.5 Existe uma notação para capturar todos os vars não estáticos por valor ou por referência e especificar smth. Mais. Exemplos: capture todos os vars não estáticos por valor, mas por captura de referência Param2
[=,&Param2](int arg1){}
Capture todos os vars não estáticos por referência, mas por captura de valor Param2
[&,Param2](int arg1){}
2. Dedução do tipo de retorno
2.1 O tipo de retorno lambda pode ser deduzido se lambda for uma expressão. Ou você pode especificá-lo explicitamente.
Se lambda tiver mais de uma expressão, o tipo de retorno deverá ser especificado por meio do tipo de retorno à direita. Além disso, sintaxe semelhante pode ser aplicada a funções automáticas e funções membro
3. Valores capturados. O que você não pode capturar
3.1 Você pode capturar apenas vars locais, não variáveis de membros do objeto.
4. Conversões
4.1 !! O Lambda não é um ponteiro de função e não é uma função anônima, mas os lambdas sem captura podem ser implicitamente convertidos em um ponteiro de função.
ps
Mais informações sobre a gramática lambda podem ser encontradas no rascunho de trabalho da linguagem de programação C ++ # 337, 2012-01-16, 5.1.2. Expressões Lambda, pág.88
No C ++ 14, o recurso extra chamado "captura de inicialização" foi adicionado. Permite executar arbitrariamente a declaração dos membros dos dados de fechamento:
auto toFloat =[](int value){returnfloat(value);};auto interpolate =[min = toFloat(0), max = toFloat(255)](int value)->float{return(value - min)/(max - min);};
Isso [&,=Param2](int arg1){}não parece ser uma sintaxe válida. O formato correto seria[&,Param2](int arg1){}
GetFree
Obrigado. Primeiro, tentei compilar esse trecho. E parece estranha assimetria nos modificadores permitidos na lista de captura // g ++ -std = c ++ 11 main.cpp -o test_bin; ./test_bin #include <stdio.h> int main () {#if 1 {int param = 0; auto f = [=, & param] (int arg1) mutável {param = arg1;}; f (111); printf ("% i \ n", param); } #endif #if 0 {int param = 0; auto f = [&, = param] (int arg1) mutável {param = arg1;}; f (111); printf ("% i \ n", param); } #endif return 0; }
bruziuz
Parece que a nova linha não é suportada no comentário. Em seguida, abri 5.1.2 expressões Lambda, p.88, "Rascunho de trabalho, padrão para linguagem de programação C ++", Número do equipamento: # 337, 2012-01-16. E olhou para a sintaxe gramatical. E você está certo. Não há como existe tal coisa como a captura via "= arg"
bruziuz
Muito obrigado, corrigi-lo na descrição e também adquirir novos conhecimentos.
bruziuz
16
Uma função lambda é uma função anônima que você cria em linha. Ele pode capturar variáveis como alguns explicaram (por exemplo, http://www.stroustrup.com/C++11FAQ.html#lambda ), mas existem algumas limitações. Por exemplo, se houver uma interface de retorno de chamada como esta,
void apply(void(*f)(int)){
f(10);
f(20);
f(30);}
você pode escrever uma função no local para usá-la como a que foi aplicada abaixo:
int col=0;void output(){
apply([](int data){
cout << data <<((++col %10)?' ':'\n');});}
devido a limitações no padrão C ++ 11. Se você quiser usar capturas, precisará confiar na biblioteca e
#include<functional>
(ou alguma outra biblioteca STL como o algoritmo para obtê-lo indiretamente) e, em seguida, trabalhe com a função std :: em vez de passar funções normais como parâmetros como este:
o motivo é que um lambda só pode ser convertido em um ponteiro de função se não tiver captura. se applyera um modelo que aceitou um functor, ele iria trabalhar
sp2danny
1
Mas o problema é que, se aplicar é uma interface existente, você pode não ter o luxo de poder declará-la de maneira diferente de uma função antiga simples. O padrão poderia ter sido projetado para permitir que uma nova instância de uma função antiga simples seja gerada toda vez que uma expressão lambda for executada, com referências codificadas codificadas geradas para as variáveis capturadas. Parece que uma função lambda é gerada em tempo de compilação. Existem outras consequências também. por exemplo, se você declarar uma variável estática, mesmo se você reavaliar a expressão lambda, não receberá uma nova variável estática.
Ted
1
o ponteiro de função costuma ser salvo e uma captura lambdas pode ficar fora do escopo. que apenas lambdas captura-less converter em função ponteiros foi por design
sp2danny
1
Você ainda precisa prestar atenção nas variáveis da pilha que estão sendo desalocadas pelo mesmo motivo. Consulte blogs.msdn.com/b/nativeconcurrency/archive/2012/01/29/…. O exemplo que escrevi com output e apply é escrito para que, se em vez disso, os ponteiros de função fossem permitidos e usados, eles também funcionariam. A coluna permanece alocada até que todas as chamadas de função de Apply tenham sido concluídas. Como você reescreveria esse código para funcionar usando a interface de aplicação existente? Você acabaria usando variáveis globais ou estáticas ou alguma transformação mais obscura do código?
Ted
1
ou talvez você queira apenas dizer que expressões lambda são rvalues e, portanto, temporárias, mas o código permanece constante (singleton / static) para que possa ser chamado no futuro. Nesse caso, talvez a função deva permanecer alocada enquanto suas capturas alocadas em pilha permanecerem alocadas. É claro que pode ficar confuso desenrolá-lo se, por exemplo, muitas variações da função forem alocadas em um loop.
Ted
12
Uma das melhores explicações de lambda expressioné dada pelo autor de C ++ Bjarne Stroustrup em seu livro ***The C++ Programming Language***capítulo 11 ( ISBN-13: 978-0321563842 ):
What is a lambda expression?
Uma expressão lambda , às vezes também chamada de
função lambda ou (estritamente falando incorretamente, mas coloquialmente) como
lambda , é uma notação simplificada para definir e usar um objeto de função anônima . Em vez de definir uma classe nomeada com um operador (), criar um objeto dessa classe e, finalmente, invocá-lo, podemos usar uma abreviação.
When would I use one?
Isso é particularmente útil quando queremos passar uma operação como argumento para um algoritmo. No contexto de interfaces gráficas do usuário (e em outros lugares), essas operações são frequentemente chamadas de retornos de chamada .
What class of problem do they solve that wasn't possible prior to their introduction?
Aqui eu acho que todas as ações feitas com a expressão lambda podem ser resolvidas sem elas, mas com muito mais código e complexidade muito maior. Expressão lambda, este é o caminho da otimização para o seu código e uma maneira de torná-lo mais atraente. Tão triste por Stroustup:
maneiras eficazes de otimizar
Some examples
via expressão lambda
void print_modulo(constvector<int>& v, ostream& os,int m)// output v[i] to os if v[i]%m==0{
for_each(begin(v),end(v),[&os,m](int x){if(x%m==0) os << x <<'\n';});}
ou via função
classModulo_print{
ostream& os;// members to hold the capture list int m;public:Modulo_print(ostream& s,int mm):os(s), m(mm){}voidoperator()(int x)const{if(x%m==0) os << x <<'\n';}};
ou mesmo
void print_modulo(constvector<int>& v, ostream& os,int m)// output v[i] to os if v[i]%m==0{classModulo_print{
ostream& os;// members to hold the capture listint m;public:Modulo_print(ostream& s,int mm):os(s), m(mm){}voidoperator()(int x)const{if(x%m==0) os << x <<'\n';}};
for_each(begin(v),end(v),Modulo_print{os,m});}
se você precisar, pode citar lambda expressioncomo abaixo:
void print_modulo(constvector<int>& v, ostream& os,int m)// output v[i] to os if v[i]%m==0{autoModulo_print=[&os,m](int x){if(x%m==0) os << x <<'\n';};
for_each(begin(v),end(v),Modulo_print);}
Ou assuma outra amostra simples
voidTestFunctions::simpleLambda(){bool sensitive =true;
std::vector<int> v = std::vector<int>({1,33,3,4,5,6,7});
sort(v.begin(),v.end(),[sensitive](int x,int y){
printf("\n%i\n", x < y);return sensitive ? x < y : abs(x)< abs(y);});
printf("sorted");
for_each(v.begin(), v.end(),[](int x){
printf("x - %i;", x);});}
irá gerar a próxima
0 0
1
0 0
1
0 0
1
0 0
1
0 0
1
0 ordenadox - 1; x - 3; x - 4; x - 5; x - 6; x - 7; x - 33;
[]- esta é uma lista de captura ou lambda introducer: se lambdasnão houver acesso ao ambiente local, podemos usá-la.
Citação do livro:
O primeiro caractere de uma expressão lambda é sempre [ . Um introdutor lambda pode assumir várias formas:
• [] : uma lista de capturas vazia. Isso implica que nenhum nome local do contexto circundante pode ser usado no corpo lambda. Para essas expressões lambda, os dados são obtidos de argumentos ou de variáveis não locais.
• [&] : captura implicitamente por referência. Todos os nomes locais podem ser usados. Todas as variáveis locais são acessadas por referência.
• [=] : captura implicitamente por valor. Todos os nomes locais podem ser usados. Todos os nomes se referem a cópias das variáveis locais obtidas no ponto de chamada da expressão lambda.
• [lista de captura]: captura explícita; a lista de captura é a lista de nomes de variáveis locais a serem capturadas (ou seja, armazenadas no objeto) por referência ou por valor. Variáveis com nomes precedidos por & são capturadas por referência. Outras variáveis são capturadas por valor. Uma lista de captura também pode conter esse e nomes seguidos por ... como elementos.
• [&, capture-list] : captura implicitamente por referência todas as variáveis locais com nomes não mencionados na lista. A lista de captura pode conter isso. Os nomes listados não podem ser precedidos por &. Variáveis nomeadas na lista de captura são capturadas por valor.
• [=, capture-list] : captura implicitamente por valor todas as variáveis locais com nomes não mencionados na lista. A lista de captura não pode conter isso. Os nomes listados devem ser precedidos por &. As variáveis nomeadas na lista de captura são capturadas por referência.
Observe que um nome local precedido por & é sempre capturado por referência e um nome local não precedido por & é sempre capturado por valor. Somente a captura por referência permite a modificação de variáveis no ambiente de chamada.
Boa explicação. Usando os loops base de gama, você pode evitar lambdas e encurtar o códigofor (int x : v) { if (x % m == 0) os << x << '\n';}
Dietrich Baumgarten
2
Bem, um uso prático que descobri é a redução do código da placa da caldeira. Por exemplo:
void process_z_vec(vector<int>& vec){auto print_2d =[](constvector<int>& board,int bsize){for(int i =0; i<bsize; i++){for(int j=0; j<bsize; j++){
cout << board[bsize*i+j]<<" ";}
cout <<"\n";}};// Do sth with the vec.
print_2d(vec,x_size);// Do sth else with the vec.
print_2d(vec,y_size);//... }
Sem o lambda, você pode precisar fazer algo para bsizecasos diferentes . Claro que você poderia criar uma função, mas e se você quiser limitar o uso dentro do escopo da função de usuário da alma? a natureza do lambda cumpre esse requisito e eu o uso nesse caso.
Os lambda's em c ++ são tratados como "função disponível on the go". sim, está literalmente em movimento, você define; use-o; e quando o escopo da função pai termina, a função lambda desaparece.
descreverei o que não existe, mas é essencial saber para todo programador em c ++
O Lambda não deve ser usado em todos os lugares e todas as funções não podem ser substituídas pelo lambda. Também não é o mais rápido comparar com a função normal. porque tem algumas despesas gerais que precisam ser tratadas pelo lambda.
certamente ajudará a reduzir o número de linhas em alguns casos. ele pode ser basicamente usado para a seção de código, que está sendo chamada na mesma função uma ou mais vezes e esse trecho de código não é necessário em nenhum outro lugar, para que você possa criar uma função autônoma para ele.
Abaixo está o exemplo básico de lambda e o que acontece em segundo plano.
int main(){int member =10;class __lambda_6_18
{int member;public:inline/*constexpr */intoperator()(int a,int b)const{return a + b + member;}public: __lambda_6_18(int _member): member{_member}{}};
__lambda_6_18 endGame = __lambda_6_18{member};
endGame.operator()(4,5);return0;}
como você pode ver, que tipo de sobrecarga ele adiciona quando você o usa. portanto, não é uma boa ideia usá-los em qualquer lugar. pode ser usado em locais onde são aplicáveis.
sim, está literalmente em movimento, você define; use-o; e quando o escopo da função pai termina, a função lambda desaparece . e se a função retornar o lambda ao chamador?
Nawaz
1
Também não é o mais rápido comparar com a função normal. porque tem algumas despesas gerais que precisam ser tratadas pelo lambda. Alguma vez você já realmente executar qualquer referência para apoiar esta reivindicação ? Pelo contrário, os modelos lambda + geralmente produzem o código mais rápido possível.
Você pode inicializar um membro const da sua classe, com uma chamada para uma função que define seu valor, devolvendo sua saída como um parâmetro de saída.
Respostas:
O problema
O C ++ inclui funções genéricas úteis como
std::for_each
estd::transform
, que podem ser muito úteis. Infelizmente, eles também podem ser bastante complicados de usar, principalmente se o functor que você deseja aplicar for exclusivo para a função específica.Se você usa apenas
f
uma vez e nesse local específico, parece exagero escrever uma classe inteira apenas para fazer algo trivial e único.No C ++ 03, você pode tentar escrever algo como o seguinte, para manter o functor local:
no entanto, isso não é permitido,
f
não pode ser passado para uma função de modelo no C ++ 03.A nova solução
O C ++ 11 introduz lambdas permite que você escreva um functor anônimo in-line para substituir o
struct f
. Para pequenos exemplos simples, isso pode ser mais fácil de ler (mantém tudo em um só lugar) e potencialmente mais simples de manter, por exemplo, da forma mais simples:Funções Lambda são apenas açúcar sintático para functores anônimos.
Tipos de retorno
Em casos simples, o tipo de retorno do lambda é deduzido para você, por exemplo:
no entanto, quando você começar a escrever lambdas mais complexas, encontrará rapidamente casos em que o tipo de retorno não pode ser deduzido pelo compilador, por exemplo:
Para resolver isso, você pode especificar explicitamente um tipo de retorno para uma função lambda, usando
-> T
:Variáveis de "captura"
Até agora, não usamos nada além do que foi passado para o lambda dentro dele, mas também podemos usar outras variáveis dentro do lambda. Se você deseja acessar outras variáveis, pode usar a cláusula capture (a
[]
da expressão), que até agora não foi utilizada nesses exemplos, por exemplo:Você pode capturar por referência e valor, que você pode especificar usando
&
e=
respectivamente:[&epsilon]
captura por referência[&]
captura todas as variáveis usadas no lambda por referência[=]
captura todas as variáveis usadas no lambda por valor[&, epsilon]
captura variáveis como com [&], mas epsilon por valor[=, &epsilon]
captura variáveis como com [=], mas epsilon por referênciaO gerado
operator()
éconst
por padrão, com a implicação de capturaconst
quando você os acessa por padrão. Isso tem o efeito de que cada chamada com a mesma entrada produziria o mesmo resultado, no entanto, você pode marcar o lambdamutable
para solicitar que ooperator()
que é produzido não sejaconst
.fonte
const
sempre ...()
- é passado como um lambda de argumento zero, mas como() const
não corresponde ao lambda, ele procura uma conversão de tipo que permita, que inclui conversão implícita -para-função-ponteiro, e chama assim! Sorrateiro!std::function<double(int, bool)> f = [](int a, bool b) -> double { ... };
Mas, geralmente, nós deixamos o compilador deduzir do tipo:auto f = [](int a, bool b) -> double { ... };
(e não se esqueça de#include <functional>
)return d < 0.00001 ? 0 : d;
é garantido o retorno duplo, quando um dos operandos é uma constante inteira (é por causa de uma regra implícita de promoção do operador?: Onde o segundo e o terceiro operando são balanceados entre si pela aritmética usual conversões, independentemente da escolha escolhida). Mudar para0.0 : d
talvez tornasse o exemplo mais fácil de entender.O que é uma função lambda?
O conceito C ++ de uma função lambda se origina no cálculo lambda e na programação funcional. Um lambda é uma função sem nome que é útil (na programação real, não na teoria) para trechos curtos de código que são impossíveis de reutilizar e que não valem a pena nomear.
Em C ++, uma função lambda é definida assim
ou em toda a sua glória
[]
é a lista de captura,()
a lista de argumentos e{}
o corpo da função.A lista de captura
A lista de captura define o que de fora do lambda deve estar disponível dentro do corpo da função e como. Pode ser:
Você pode misturar qualquer um dos itens acima em uma lista separada por vírgulas
[x, &y]
.A lista de argumentos
A lista de argumentos é a mesma que em qualquer outra função C ++.
O corpo da função
O código que será executado quando o lambda for realmente chamado.
Dedução de tipo de retorno
Se um lambda tiver apenas uma instrução de retorno, o tipo de retorno poderá ser omitido e terá o tipo implícito de
decltype(return_statement)
.Mutável
Se um lambda é marcado como mutável (por exemplo
[]() mutable { }
), é permitido alterar os valores que foram capturados por valor.Casos de uso
A biblioteca definida pelo padrão ISO se beneficia muito das lambdas e aumenta a usabilidade em várias barras, pois agora os usuários não precisam desorganizar seu código com pequenos functores em algum escopo acessível.
C ++ 14
Em C ++ 14, lambdas foram estendidas por várias propostas.
Capturas Lambda inicializadas
Um elemento da lista de captura agora pode ser inicializado com
=
. Isso permite renomear variáveis e capturar movendo-se. Um exemplo retirado do padrão:e um retirado da Wikipedia mostrando como capturar com
std::move
:Lambdas genéricos
Agora, as lambdas podem ser genéricas (
auto
seria equivalente aT
aqui se houvesseT
um argumento de modelo de tipo em algum lugar do escopo circundante):Dedução de tipo de retorno aprimorada
O C ++ 14 permite tipos de retorno deduzidos para todas as funções e não o restringe às funções do formulário
return expression;
. Isso também se estende às lambdas.fonte
r = &x; r += 2;
mas isso acontece com o valor original de 4. #As expressões lambda são normalmente usadas para encapsular algoritmos para que possam ser passados para outra função. No entanto, é possível executar um lambda imediatamente após a definição :
é funcionalmente equivalente a
Isso torna as expressões lambda uma ferramenta poderosa para refatorar funções complexas . Você começa envolvendo uma seção de código em uma função lambda, como mostrado acima. O processo de parametrização explícita pode ser realizado gradualmente com testes intermediários após cada etapa. Depois de ter o bloco de código totalmente parametrizado (como demonstrado pela remoção do
&
), você pode mover o código para um local externo e torná-lo uma função normal.Da mesma forma, você pode usar expressões lambda para inicializar variáveis com base no resultado de um algoritmo ...
Como uma maneira de particionar a lógica do programa , você pode até achar útil passar uma expressão lambda como argumento para outra expressão lambda ...
As expressões lambda também permitem criar funções aninhadas nomeadas , que podem ser uma maneira conveniente de evitar lógica duplicada. Usar lambdas nomeadas também tende a ser um pouco mais fácil para os olhos (em comparação com lambdas anônimas inline) ao passar uma função não trivial como parâmetro para outra função. Nota: não esqueça o ponto-e-vírgula após a chave de fechamento.
Se a criação de perfil subsequente revelar uma sobrecarga significativa de inicialização para o objeto de função, você poderá reescrever isso como uma função normal.
fonte
if
declarações:if ([i]{ for (char j : i) if (!isspace(j)) return false ; return true ; }()) // i is all whitespace
, assumindo quei
é umstd::string
[](){}();
.(lambda: None)()
sintaxe do Python é muito mais legível.main() {{{{((([](){{}}())));}}}}
Respostas
P: O que é uma expressão lambda no C ++ 11?
R: Sob o capô, é o objeto de uma classe gerada automaticamente com operador de sobrecarga () const . Esse objeto é chamado de fechamento e criado pelo compilador. Esse conceito de 'fechamento' está próximo do conceito de ligação do C ++ 11. Mas lambdas normalmente geram código melhor. E chamadas através de fechamentos permitem inlining completo.
P: Quando eu usaria um?
R: Para definir "lógica simples e pequena" e solicitar que o compilador execute a geração da pergunta anterior. Você fornece ao compilador algumas expressões que você deseja que esteja dentro do operador (). Todos os outros compiladores de coisas serão gerados para você.
P: Que tipo de problema eles resolvem que não era possível antes da introdução?
R: É algum tipo de açúcar de sintaxe, como sobrecarga de operadores, em vez de funções para operações personalizadas de adição e subrato ... Mas economiza mais linhas de código desnecessário para envolver 1-3 linhas de lógica real em algumas classes e etc.! Alguns engenheiros pensam que, se o número de linhas for menor, haverá menos chance de cometer erros (também acho)
Exemplo de uso
Extras sobre lambdas, não cobertos por perguntas. Ignore esta seção se você não tiver interesse
1. Valores capturados. O que você pode capturar
1.1 Você pode fazer referência a uma variável com duração de armazenamento estático em lambdas. Todos eles são capturados.
1.2 Você pode usar o lambda para capturar valores "por valor". Nesse caso, os vars capturados serão copiados para o objeto de função (fechamento).
1.3 Você pode capturar ser referência. & - neste contexto significa referência, não ponteiros.
1.4 Existe notação para capturar todos os vars não estáticos por valor ou por referência
1.5 Existe uma notação para capturar todos os vars não estáticos por valor ou por referência e especificar smth. Mais. Exemplos: capture todos os vars não estáticos por valor, mas por captura de referência Param2
Capture todos os vars não estáticos por referência, mas por captura de valor Param2
2. Dedução do tipo de retorno
2.1 O tipo de retorno lambda pode ser deduzido se lambda for uma expressão. Ou você pode especificá-lo explicitamente.
Se lambda tiver mais de uma expressão, o tipo de retorno deverá ser especificado por meio do tipo de retorno à direita. Além disso, sintaxe semelhante pode ser aplicada a funções automáticas e funções membro
3. Valores capturados. O que você não pode capturar
3.1 Você pode capturar apenas vars locais, não variáveis de membros do objeto.
4. Conversões
4.1 !! O Lambda não é um ponteiro de função e não é uma função anônima, mas os lambdas sem captura podem ser implicitamente convertidos em um ponteiro de função.
ps
Mais informações sobre a gramática lambda podem ser encontradas no rascunho de trabalho da linguagem de programação C ++ # 337, 2012-01-16, 5.1.2. Expressões Lambda, pág.88
No C ++ 14, o recurso extra chamado "captura de inicialização" foi adicionado. Permite executar arbitrariamente a declaração dos membros dos dados de fechamento:
fonte
[&,=Param2](int arg1){}
não parece ser uma sintaxe válida. O formato correto seria[&,Param2](int arg1){}
Uma função lambda é uma função anônima que você cria em linha. Ele pode capturar variáveis como alguns explicaram (por exemplo, http://www.stroustrup.com/C++11FAQ.html#lambda ), mas existem algumas limitações. Por exemplo, se houver uma interface de retorno de chamada como esta,
você pode escrever uma função no local para usá-la como a que foi aplicada abaixo:
Mas você não pode fazer isso:
devido a limitações no padrão C ++ 11. Se você quiser usar capturas, precisará confiar na biblioteca e
(ou alguma outra biblioteca STL como o algoritmo para obtê-lo indiretamente) e, em seguida, trabalhe com a função std :: em vez de passar funções normais como parâmetros como este:
fonte
apply
era um modelo que aceitou um functor, ele iria trabalharUma das melhores explicações de
lambda expression
é dada pelo autor de C ++ Bjarne Stroustrup em seu livro***The C++ Programming Language***
capítulo 11 ( ISBN-13: 978-0321563842 ):What is a lambda expression?
When would I use one?
What class of problem do they solve that wasn't possible prior to their introduction?
Aqui eu acho que todas as ações feitas com a expressão lambda podem ser resolvidas sem elas, mas com muito mais código e complexidade muito maior. Expressão lambda, este é o caminho da otimização para o seu código e uma maneira de torná-lo mais atraente. Tão triste por Stroustup:
Some examples
via expressão lambda
ou via função
ou mesmo
se você precisar, pode citar
lambda expression
como abaixo:Ou assuma outra amostra simples
irá gerar a próxima
[]
- esta é uma lista de captura oulambda introducer
: selambdas
não houver acesso ao ambiente local, podemos usá-la.Citação do livro:
Additional
Lambda expression
formatoReferências adicionais:
fonte
for (int x : v) { if (x % m == 0) os << x << '\n';}
Bem, um uso prático que descobri é a redução do código da placa da caldeira. Por exemplo:
Sem o lambda, você pode precisar fazer algo para
bsize
casos diferentes . Claro que você poderia criar uma função, mas e se você quiser limitar o uso dentro do escopo da função de usuário da alma? a natureza do lambda cumpre esse requisito e eu o uso nesse caso.fonte
Os lambda's em c ++ são tratados como "função disponível on the go". sim, está literalmente em movimento, você define; use-o; e quando o escopo da função pai termina, a função lambda desaparece.
O c ++ o introduziu no c ++ 11 e todos começaram a usá-lo como em todos os lugares possíveis. o exemplo e o que é lambda pode ser encontrado aqui https://en.cppreference.com/w/cpp/language/lambda
descreverei o que não existe, mas é essencial saber para todo programador em c ++
O Lambda não deve ser usado em todos os lugares e todas as funções não podem ser substituídas pelo lambda. Também não é o mais rápido comparar com a função normal. porque tem algumas despesas gerais que precisam ser tratadas pelo lambda.
certamente ajudará a reduzir o número de linhas em alguns casos. ele pode ser basicamente usado para a seção de código, que está sendo chamada na mesma função uma ou mais vezes e esse trecho de código não é necessário em nenhum outro lugar, para que você possa criar uma função autônoma para ele.
Abaixo está o exemplo básico de lambda e o que acontece em segundo plano.
Código de usuário:
Como a compilação a expande:
como você pode ver, que tipo de sobrecarga ele adiciona quando você o usa. portanto, não é uma boa ideia usá-los em qualquer lugar. pode ser usado em locais onde são aplicáveis.
fonte
Um problema que ele resolve: Código mais simples que lambda para uma chamada no construtor que usa uma função de parâmetro de saída para inicializar um membro const
Você pode inicializar um membro const da sua classe, com uma chamada para uma função que define seu valor, devolvendo sua saída como um parâmetro de saída.
fonte