O que é uma expressão lambda no C ++ 11?

1488

O que é uma expressão lambda no C ++ 11? Quando eu usaria um? Que classe de problemas eles resolvem que não era possível antes da introdução?

Alguns exemplos e casos de uso seriam úteis.

Nawaz
fonte
14
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 {
    void operator()(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 {
    void operator()(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:

void func4(std::vector<double>& v) {
    std::transform(v.begin(), v.end(), v.begin(),
        [](double d) {
            if (d < 0.0001) {
                return 0;
            } else {
                return d;
            }
        });
}

Para resolver isso, você pode especificar explicitamente um tipo de retorno para uma função lambda, usando -> T:

void func4(std::vector<double>& v) {
    std::transform(v.begin(), v.end(), v.begin(),
        [](double d) -> double {
            if (d < 0.0001) {
                return 0;
            } else {
                return d;
            }
        });
}

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:

void func5(std::vector<double>& v, const double& epsilon) {
    std::transform(v.begin(), v.end(), v.begin(),
        [epsilon](double d) -> double {
            if (d < epsilon) {
                return 0;
            } else {
                return d;
            }
        });
}

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.

Flexo
fonte
9
@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:

  1. um valor: [x]
  2. uma referência [& x]
  3. qualquer variável atualmente no escopo por referência [&]
  4. 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_unique
auto 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.

pmr
fonte
2
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 :

[&](){ ...your code... }(); // immediately executed lambda expression

é funcionalmente equivalente a

{ ...your code... } // simple code block

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 ...

[&]( std::function<void()> algorithm ) // wrapper section
   {
   ...your wrapper code...
   algorithm();
   ...your wrapper code...
   }
([&]() // algorithm section
   {
   ...your algorithm code...
   });

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.

nobar
fonte
11
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.

[=](int arg1)->trailing_return_type{return trailing_return_type();}

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

  1. 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

  2. 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) { return float(value);};
    auto interpolate = [min = toFloat(0), max = toFloat(255)](int value)->float { return (value - min) / (max - min);};
bruziuz
fonte
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');
    });
}

Mas você não pode fazer isso:

void output(int n) {
    int col=0;
    apply([&col,n](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:

#include <functional>
void apply(std::function<void(int)> f) {
    f(10);
    f(20);
    f(30);
}
void output(int width) {
    int col;
    apply([width,&col](int data) {
        cout << data << ((++col % width) ? ' ' : '\n');
    });
}
Ted
fonte
1
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(const vector<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

class Modulo_print {
         ostream& os; // members to hold the capture list int m;
     public:
         Modulo_print(ostream& s, int mm) :os(s), m(mm) {} 
         void operator()(int x) const
           { 
             if (x%m==0) os << x << '\n'; 
           }
};

ou mesmo

void print_modulo(const vector<int>& v, ostream& os, int m) 
     // output v[i] to os if v[i]%m==0
{
    class Modulo_print {
        ostream& os; // members to hold the capture list
        int m; 
        public:
           Modulo_print (ostream& s, int mm) :os(s), m(mm) {}
           void operator()(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(const vector<int>& v, ostream& os, int m)
    // output v[i] to os if v[i]%m==0
{
      auto Modulo_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

void TestFunctions::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.

Additional

Lambda expression formato

insira a descrição da imagem aqui

Referências adicionais:

gbk
fonte
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 = [](const vector<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.

Misgevolution
fonte
2

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:

int main()
{
  // Lambda & auto
  int member=10;
  auto endGame = [=](int a, int b){ return a+b+member;};

  endGame(4,5);

  return 0;

}

Como a compilação a expande:

int main()
{
  int member = 10;

  class __lambda_6_18
  {
    int member;
    public: 
    inline /*constexpr */ int operator()(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);

  return 0;
}

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.

Sachin Nale
fonte
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.
Nawaz
1

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.

sergiol
fonte
Isso também pode ser feito com uma função simples, que é o que a resposta aceita para a pergunta à qual você vinculou diz fazer.
precisa saber é o seguinte