Che cos'è un'espressione lambda in C ++ 11?


1488

Che cos'è un'espressione lambda in C ++ 11? Quando dovrei usarne uno? Quale classe di problemi risolvono che non era possibile prima della loro introduzione?

Alcuni esempi e casi d'uso sarebbero utili.


14
Ho visto un caso in cui la lambda era molto utile: un mio collega stava realizzando un codice che ha milioni di iterazioni per risolvere un problema di ottimizzazione dello spazio. L'algoritmo era molto più veloce quando si utilizza una lambda di una funzione corretta! Il compilatore è Visual C ++ 2013.
sergiol,

Risposte:


1491

Il problema

C ++ include utili funzioni generiche come std::for_eache std::transform, che possono essere molto utili. Purtroppo possono anche essere abbastanza ingombrante per l'uso, in particolare se il funtore si desidera applicare è unica per la particolare funzione.

#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 usi solo funa volta e in quel posto specifico, sembra eccessivo scrivere un'intera classe solo per fare qualcosa di banale e una tantum.

In C ++ 03 potresti essere tentato di scrivere qualcosa di simile al seguente, per mantenere il funzione locale:

void func2(std::vector<int>& v) {
  struct {
    void operator()(int) {
       // do something
    }
  } f;
  std::for_each(v.begin(), v.end(), f);
}

tuttavia ciò non è consentito, fnon può essere passato a una funzione modello in C ++ 03.

La nuova soluzione

C ++ 11 introduce lambdas che consente di scrivere un funzione anonimo in linea per sostituire il struct f. Per piccoli semplici esempi questo può essere più pulito da leggere (mantiene tutto in un unico posto) e potenzialmente più semplice da mantenere, ad esempio nella forma più semplice:

void func3(std::vector<int>& v) {
  std::for_each(v.begin(), v.end(), [](int) { /* do something here*/ });
}

Le funzioni lambda sono solo zucchero sintattico per i professionisti anonimi.

Tipi di ritorno

In casi semplici viene dedotto il tipo di ritorno della lambda, ad es .:

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

tuttavia, quando si inizia a scrivere lambda più complessi, si verificheranno rapidamente casi in cui il tipo di ritorno non può essere dedotto dal compilatore, ad esempio:

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

Per risolvere questo, ti è permesso specificare esplicitamente un tipo di ritorno per una funzione 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;
            }
        });
}

"Catturare" le variabili

Finora non abbiamo usato altro che ciò che è stato passato al lambda al suo interno, ma possiamo anche usare altre variabili, all'interno del lambda. Se si desidera accedere ad altre variabili, è possibile utilizzare la clausola di acquisizione ( []l'espressione), che finora non è stata utilizzata in questi esempi, ad esempio:

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

Puoi acquisire sia per riferimento che per valore, che puoi specificare usando &e =rispettivamente:

  • [&epsilon] cattura per riferimento
  • [&] cattura tutte le variabili utilizzate nella lambda per riferimento
  • [=] cattura tutte le variabili utilizzate in lambda per valore
  • [&, epsilon] cattura variabili come con [&], ma epsilon per valore
  • [=, &epsilon] cattura variabili come con [=], ma epsilon per riferimento

Il generato operator()è constper impostazione predefinita, con l'implicazione che saranno le acquisizioni constquando si accede a loro per impostazione predefinita. Ciò ha l'effetto che ogni chiamata con lo stesso input produrrebbe lo stesso risultato, tuttavia è possibile contrassegnare la lambda in modomutable da richiedere che operator()ciò che viene prodotto non lo sia const.


9
@Yakk sei stato intrappolato. I lambda senza acquisizione hanno una conversione implicita in puntatori del tipo di funzione. la funzione di conversione è constsempre ...
Johannes Schaub - litb

2
@ JohannesSchaub-litb oh subdolo - e succede quando invochi ()- viene passato come lambda a argomento zero, ma poiché () constnon corrisponde al lambda, cerca una conversione di tipo che lo consenta, che include il cast implicito -to-funzione-puntatore e quindi lo chiama! Subdolo!
Yakk - Adam Nevraumont

2
Interessante: inizialmente pensavo che le lambda fossero funzioni anonime piuttosto che funzioni , ed ero confuso su come funzionavano le catture.
user253751

50
Se vuoi usare lambdas come variabili nel tuo programma, puoi usare: std::function<double(int, bool)> f = [](int a, bool b) -> double { ... }; Ma di solito, lasciamo che il compilatore deduca il tipo: auto f = [](int a, bool b) -> double { ... }; (e non dimenticare di #include <functional>)
Evert Heylen

11
Suppongo che non tutti capiscano perché return d < 0.00001 ? 0 : d;è garantito il ritorno doppio, quando uno degli operandi è una costante intera (è a causa di una regola di promozione implicita dell'operatore?: Dove il 2o e 3o operando sono bilanciati l'uno contro l'altro attraverso la solita aritmetica conversioni indipendentemente da quale viene scelto). Cambiare in 0.0 : dforse renderebbe l'esempio più facile da capire.
Lundin,

831

Cos'è una funzione lambda?

Il concetto C ++ di una funzione lambda ha origine nel calcolo lambda e nella programmazione funzionale. Una lambda è una funzione senza nome che è utile (nella programmazione attuale, non in teoria) per brevi frammenti di codice che sono impossibili da riutilizzare e non meritano di essere nominati.

In C ++ una funzione lambda è definita in questo modo

[]() { } // barebone lambda

o in tutta la sua gloria

[]() mutable -> T { } // T is the return type, still lacking throw()

[]è l'elenco di acquisizione, ()l'elenco degli argomenti e {}il corpo della funzione.

L'elenco di acquisizione

L'elenco di acquisizione definisce ciò che dall'esterno della lambda dovrebbe essere disponibile all'interno del corpo della funzione e come. Può essere:

  1. un valore: [x]
  2. un riferimento [& x]
  3. qualsiasi variabile attualmente inclusa nell'ambito di riferimento [&]
  4. uguale a 3, ma per valore [=]

È possibile combinare uno dei precedenti in un elenco separato da virgole [x, &y].

L'elenco degli argomenti

L'elenco degli argomenti è lo stesso di qualsiasi altra funzione C ++.

Il corpo della funzione

Il codice che verrà eseguito quando viene effettivamente chiamato lambda.

Deduzione del tipo restituito

Se un lambda ha solo un'istruzione return, il tipo restituito può essere omesso e ha il tipo implicito di decltype(return_statement).

Mutevole

Se un lambda è contrassegnato come mutabile (ad es. []() mutable { }) È consentito mutare i valori che sono stati catturati dal valore.

Casi d'uso

La libreria definita dallo standard ISO beneficia fortemente di lambdas e aumenta l'usabilità di parecchi bar poiché ora gli utenti non devono ingombrare il loro codice con piccoli funzioni in un ambito accessibile.

C ++ 14

In C ++ 14 le lambda sono state estese da varie proposte.

Catture lambda inizializzate

Ora è possibile inizializzare un elemento dell'elenco di acquisizione =. Ciò consente di rinominare le variabili e di acquisirle spostandole. Un esempio preso dallo standard:

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 uno preso da Wikipedia che mostra come catturare con std::move:

auto ptr = std::make_unique<int>(10); // See below for std::make_unique
auto lambda = [ptr = std::move(ptr)] {return *ptr;};

Lambdas generici

Lambdas ora può essere generico ( autosarebbe equivalente a Tqui se Tfosse un argomento tipo template da qualche parte nell'ambito circostante):

auto lambda = [](auto x, auto y) {return x + y;};

Detrazione del tipo di ritorno migliorata

C ++ 14 consente tipi di restituzione dedotti per ogni funzione e non lo limita alle funzioni del modulo return expression;. Questo si estende anche agli lambda.


2
Nel tuo esempio per le acquisizioni lambda inizializzate sopra, perché finisci la funzione lamba con il () ;? Questo appare come [] () {} (); invece di [](){};. Inoltre, il valore di x non dovrebbe essere 5?
Ramakrishnan Kannan,

7
@RamakrishnanKannan: 1) i () sono lì per chiamare il lambda subito dopo averlo definito e darti il ​​suo valore di ritorno. La variabile y è un numero intero, non lambda. 2) No, x = 5 è locale per lambda (un'acquisizione per valore che ha lo stesso nome della variabile di ambito esterno x), quindi viene restituito x + 2 = 5 + 2. La riassegnazione della variabile esterna x avviene attraverso il riferimento r:, r = &x; r += 2;ma ciò accade al valore originale di 4.
The Vee

168

Le espressioni lambda vengono in genere utilizzate per incapsulare algoritmi in modo che possano essere passate a un'altra funzione. Tuttavia, è possibile eseguire un lambda immediatamente dopo la definizione :

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

è funzionalmente equivalente a

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

Ciò rende le espressioni lambda un potente strumento per il refactoring di funzioni complesse . Si inizia avvolgendo una sezione di codice in una funzione lambda come mostrato sopra. Il processo di parametrizzazione esplicita può quindi essere eseguito gradualmente con test intermedi dopo ogni passaggio. Una volta che il blocco di codice è completamente parametrizzato (come dimostrato dalla rimozione di &), è possibile spostare il codice in una posizione esterna e renderlo una normale funzione.

Allo stesso modo, puoi usare le espressioni lambda per inizializzare le variabili in base al risultato di un algoritmo ...

int a = []( int b ){ int r=1; while (b>0) r*=b--; return r; }(5); // 5!

Come modo di partizionare la logica del programma , potresti persino trovare utile passare un'espressione lambda come argomento a un'altra espressione lambda ...

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

Le espressioni lambda consentono inoltre di creare funzioni nidificate denominate , che può essere un modo conveniente per evitare la logica duplicata. L'uso del nome lambdas tende anche ad essere un po 'più semplice per gli occhi (rispetto agli lambda in linea anonimi) quando si passa una funzione non banale come parametro a un'altra funzione. Nota: non dimenticare il punto e virgola dopo la parentesi graffa di chiusura.

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 la profilazione successiva rivela un notevole sovraccarico di inizializzazione per l'oggetto funzione, è possibile scegliere di riscriverlo come una normale funzione.


11
Ti sei reso conto che questa domanda è stata posta 1,5 anni fa e che l'ultima attività è stata quasi 1 anno fa? Ad ogni modo, stai contribuendo con alcune idee interessanti che non ho mai visto prima!
Piotr99

7
Grazie per il suggerimento simultaneo di definire ed eseguire! Penso che valga la pena notare che funziona come un contidion per le ifdichiarazioni :, if ([i]{ for (char j : i) if (!isspace(j)) return false ; return true ; }()) // i is all whitespacesupponendo che isia unstd::string
Blacklight Shining

74
Quindi il seguente è un espressione giuridica: [](){}();.
nobar,

8
Ugh! La (lambda: None)()sintassi di Python è molto più leggibile.
dan04,

9
@nobar - hai ragione, ho sbagliato a scrivere. Questo è legale (l'ho provato questa volta)main() {{{{((([](){{}}())));}}}}
Mark Lakata

38

risposte

D: Cos'è un'espressione lambda in C ++ 11?

A: Sotto il cofano, è l'oggetto di una classe autogenerata con operatore di sovraccarico () const . Tale oggetto è chiamato chiusura e creato dal compilatore. Questo concetto di "chiusura" è vicino al concetto di associazione di C ++ 11. Ma i lambda in genere generano un codice migliore. E le chiamate attraverso le chiusure consentono un allineamento completo.

Q: Quando dovrei usarne uno?

A: Definire "logica semplice e piccola" e chiedere al compilatore di eseguire la generazione dalla domanda precedente. Dai a un compilatore alcune espressioni che vuoi essere all'interno dell'operatore (). Tutti gli altri compilatori di roba genereranno per te.

D: Quale classe di problemi risolvono che non era possibile prima della loro introduzione?

A: È una specie di zucchero di sintassi come il sovraccarico degli operatori invece delle funzioni per operazioni personalizzate di aggiunta, subrtact ... Ma salva più righe di codice non necessario per avvolgere 1-3 righe di logica reale in alcune classi, ecc.! Alcuni ingegneri pensano che se il numero di linee è inferiore, allora ci sono meno possibilità di commettere errori (lo penso anche io)

Esempio di utilizzo

auto x = [=](int arg1){printf("%i", arg1); };
void(*f)(int) = x;
f(1);
x(1);

Extra su lambda, non coperti dalla domanda. Ignora questa sezione se non ti interessa

1. Valori acquisiti. Cosa puoi catturare

1.1. È possibile fare riferimento a una variabile con durata di memorizzazione statica in lambdas. Sono tutti catturati.

1.2. È possibile utilizzare lambda per acquisire i valori "per valore". In tal caso i vars catturati verranno copiati nell'oggetto funzione (chiusura).

[captureVar1,captureVar2](int arg1){}

1.3. È possibile acquisire come riferimento. & - in questo contesto significano riferimento, non puntatori.

   [&captureVar1,&captureVar2](int arg1){}

1.4. Esiste una notazione per acquisire tutte le variabili non statiche per valore o per riferimento

  [=](int arg1){} // capture all not-static vars by value

  [&](int arg1){} // capture all not-static vars by reference

1.5. Esiste una notazione per acquisire tutte le variabili non statiche per valore o per riferimento e specificare smth. Di Più. Esempi: cattura tutte le variabili non statiche per valore, ma per riferimento cattura Param2

[=,&Param2](int arg1){} 

Cattura tutte le variabili non statiche per riferimento, ma per valore cattura Param2

[&,Param2](int arg1){} 

2. Detrazione del tipo restituito

2.1. Il tipo di ritorno lambda può essere dedotto se lambda è un'espressione. Oppure puoi specificarlo esplicitamente.

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

Se lambda ha più di un'espressione, è necessario specificare il tipo restituito tramite il tipo restituito finale. Inoltre, sintassi simile può essere applicata alle funzioni automatiche e alle funzioni membro

3. Valori acquisiti. Cosa non puoi catturare

3.1. È possibile acquisire solo variabili locali, non variabili membro dell'oggetto.

4. Сonversions

4.1 !! Lambda non è un puntatore a funzione e non è una funzione anonima, ma i lambda senza acquisizione possono essere convertiti implicitamente in un puntatore a funzione.

ps

  1. Ulteriori informazioni sulla grammatica lambda sono disponibili nella bozza di lavoro per il linguaggio di programmazione C ++ # 337, 2012-01-16, 5.1.2. Espressioni lambda, p.88

  2. In C ++ 14 sono state aggiunte le funzionalità extra che sono state denominate "init capture". Permette di eseguire una dichiarazione arbitraria dei membri dei dati di chiusura:

    auto toFloat = [](int value) { return float(value);};
    auto interpolate = [min = toFloat(0), max = toFloat(255)](int value)->float { return (value - min) / (max - min);};

Questa [&,=Param2](int arg1){}non sembra essere una sintassi valida. La forma corretta sarebbe[&,Param2](int arg1){}
GetFree,

Grazie. Per prima cosa ho provato a compilare questo frammento. E sembra strana assimetria nei modificatori consentiti nell'elenco di acquisizione // 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) mutabile {param = arg1;}; f (111); printf ("% i \ n", param); } #endif #if 0 {int param = 0; auto f = [&, = param] (int arg1) mutabile {param = arg1;}; f (111); printf ("% i \ n", param); } #endif return 0; }
bruziuz,

Sembra che la nuova riga non sia supportata nei commenti. Quindi ho aperto le espressioni Lambda 5.1.2, p.88, "Working Draft, Standard for Programming Language C ++", Numero documento: # 337, 2012-01-16. E ho esaminato la sintassi grammaticale. E hai ragione. Non esiste qualcosa come l'acquisizione tramite "= arg"
bruziuz,

Grazie mille, risolto nella descrizione e acquisito anche nuove conoscenze.
bruziuz,

16

Una funzione lambda è una funzione anonima che si crea in linea. Può catturare variabili come alcuni hanno spiegato (ad esempio http://www.stroustrup.com/C++11FAQ.html#lambda ) ma ci sono alcune limitazioni. Ad esempio, se esiste un'interfaccia di callback come questa,

void apply(void (*f)(int)) {
    f(10);
    f(20);
    f(30);
}

puoi scrivere una funzione sul posto per usarla come quella passata da applicare di seguito:

int col=0;
void output() {
    apply([](int data) {
        cout << data << ((++col % 10) ? ' ' : '\n');
    });
}

Ma non puoi farlo:

void output(int n) {
    int col=0;
    apply([&col,n](int data) {
        cout << data << ((++col % 10) ? ' ' : '\n');
    });
}

a causa di limitazioni nello standard C ++ 11. Se si desidera utilizzare acquisizioni, è necessario fare affidamento sulla libreria e

#include <functional> 

(o qualche altra libreria STL come l'algoritmo per ottenerlo indirettamente) e quindi lavorare con std :: function invece di passare le normali funzioni come parametri come questo:

#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');
    });
}

1
il motivo è che un lambda può convertire solo in un puntatore a funzione, se non ha acquisizione. se applyfosse un modello che ha accettato un funzione, funzionerebbe
sp2danny il

1
Ma il problema è che se si applica un'interfaccia esistente, potresti non avere il lusso di poterlo dichiarare diversamente da una semplice vecchia funzione. Lo standard avrebbe potuto essere progettato per consentire la generazione di una nuova istanza di una semplice vecchia funzione ogni volta che viene eseguita un'espressione lambda di questo tipo, con riferimenti generati hardcoded alle variabili acquisite. Sembra che al momento della compilazione venga generata una funzione lambda. Ci sono anche altre conseguenze. ad esempio, se si dichiara una variabile statica, anche se si rivaluta l'espressione lambda, non si ottiene una nuova variabile statica.
Ted

1
il puntatore a funzione è spesso pensato per essere salvato, e una cattura lambdas può andare al di fuori dell'ambito. che solo i lambda
privi di

1
Devi ancora prestare attenzione alle variabili dello stack che sono state deallocate per lo stesso motivo in entrambi i casi. Vedi blogs.msdn.com/b/nativeconcurrency/archive/2012/01/29/… L'esempio che ho scritto con output e application è scritto in modo che se invece fossero consentiti e utilizzati i puntatori a funzioni, funzionerebbero anche loro. Il col rimane allocato fino al termine di tutte le chiamate di funzione di apply. Come riscriveresti questo codice per funzionare usando l'interfaccia di applicazione esistente? Finiresti per usare variabili globali o statiche o qualche trasformazione più oscura del codice?
Ted

1
o forse intendi semplicemente che le espressioni lambda sono valori e quindi temporanei, ma il codice rimane costante (singleton / statico) in modo che possa essere chiamato in futuro. In tal caso, forse la funzione dovrebbe rimanere allocata fintanto che le catture allocate nello stack rimangono allocate. Ovviamente potrebbe diventare disordinato srotolarlo se ad esempio molte varianti della funzione sono allocate in un ciclo.
Ted

12

Una delle migliori spiegazioni di lambda expressionè data dall'autore del C ++ Bjarne Stroustrup nel suo libro ***The C++ Programming Language***capitolo 11 ( ISBN-13: 978-0321563842 ):

What is a lambda expression?

Un'espressione lambda , talvolta indicato anche come una lambda funzione o (in senso stretto correttamente, ma colloquialmente) come lambda , è una notazione semplificata per definire e utilizzare un oggetto funzione anonima . Invece di definire una classe nominata con un operatore (), in seguito creando un oggetto di quella classe e infine invocandolo, possiamo usare una scorciatoia.

When would I use one?

Ciò è particolarmente utile quando vogliamo passare un'operazione come argomento a un algoritmo. Nel contesto delle interfacce utente grafiche (e altrove), tali operazioni sono spesso chiamate callback .

What class of problem do they solve that wasn't possible prior to their introduction?

Qui immagino che ogni azione fatta con lambda possa essere risolta senza di loro, ma con molto più codice e una complessità molto maggiore. Espressione lambda: questo è il modo di ottimizzare il tuo codice e un modo per renderlo più attraente. Come triste da Stroustup:

modi efficaci di ottimizzazione

Some examples

tramite espressione 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';
         });
}

o tramite la funzione

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

o anche

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 ti serve puoi nominare lambda expressioncome di seguito:

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

O supponiamo un altro semplice esempio

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

genererà il prossimo

0

1

0

1

0

1

0

1

0

1

0 ordinatix - 1; x - 3; x - 4; x - 5; x - 6; x - 7; x - 33;

[]- questo è un elenco di acquisizione o lambda introducer: se lambdasnon è necessario l'accesso al proprio ambiente locale, è possibile utilizzarlo.

Citazione dal libro:

Il primo carattere di un'espressione lambda è sempre [ . Un introduttore lambda può assumere varie forme:

[] : un elenco di acquisizione vuoto. Ciò implica che nessun nome locale dal contesto circostante può essere usato nel corpo lambda. Per tali espressioni lambda, i dati sono ottenuti da argomenti o da variabili non locali.

[&] : cattura implicitamente per riferimento. Tutti i nomi locali possono essere utilizzati. Tutte le variabili locali sono accessibili per riferimento.

[=] : cattura implicitamente per valore. Tutti i nomi locali possono essere utilizzati. Tutti i nomi si riferiscono a copie delle variabili locali prese nel punto di richiamo dell'espressione lambda.

[elenco di acquisizione]: acquisizione esplicita; l'elenco di acquisizione è l'elenco di nomi di variabili locali da acquisire (ovvero, memorizzati nell'oggetto) per riferimento o per valore. Le variabili con nomi precedute da & sono acquisite per riferimento. Altre variabili vengono acquisite per valore. Un elenco di acquisizione può contenere anche questo e nomi seguiti da ... come elementi.

[&, elenco di acquisizione] : acquisisce implicitamente per riferimento tutte le variabili locali con nomi non menzionati nell'elenco. L'elenco di acquisizione può contenere questo. I nomi elencati non possono essere preceduti da &. Le variabili nominate nell'elenco di acquisizione vengono acquisite per valore.

[=, elenco di acquisizione] : acquisisce implicitamente per valore tutte le variabili locali con nomi non menzionati nell'elenco. L'elenco di acquisizione non può contenere questo. I nomi elencati devono essere preceduti da &. Le variabili indicate nell'elenco di acquisizione vengono acquisite per riferimento.

Si noti che un nome locale preceduto da & viene sempre catturato per riferimento e un nome locale non preceduto da & viene sempre catturato dal valore. Solo l'acquisizione per riferimento consente la modifica delle variabili nell'ambiente chiamante.

Additional

Lambda expression formato

inserisci qui la descrizione dell'immagine

Riferimenti aggiuntivi:


Bella spiegazione. Utilizzando il range-based per i loop, puoi evitare lambda e abbreviare il codicefor (int x : v) { if (x % m == 0) os << x << '\n';}
Dietrich Baumgarten,

2

Bene, un uso pratico che ho scoperto è la riduzione del codice della piastra della caldaia. Per esempio:

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);
  //... 
}

Senza lambda, potresti dover fare qualcosa per bsizecasi diversi . Ovviamente potresti creare una funzione ma cosa succede se vuoi limitare l'utilizzo nell'ambito della funzione utente soul? la natura di lambda soddisfa questo requisito e lo uso per quel caso.


2

Le lambda in c ++ sono trattate come "on the go available available". sì, è letteralmente in movimento, lo definisci; usalo; e al termine dell'ambito della funzione genitore, la funzione lambda scompare.

c ++ lo ha introdotto in c ++ 11 e tutti hanno iniziato a usarlo come in ogni luogo possibile. l'esempio e cos'è lambda è disponibile qui https://en.cppreference.com/w/cpp/language/lambda

descriverò ciò che non c'è ma essenziale da sapere per ogni programmatore c ++

Lambda non è pensato per essere usato ovunque e ogni funzione non può essere sostituita con lambda. Inoltre, non è il più veloce rispetto alla normale funzione. perché ha delle spese generali che devono essere gestite da lambda.

sicuramente aiuterà a ridurre il numero di linee in alcuni casi. può essere sostanzialmente utilizzato per la sezione di codice, che viene chiamata nella stessa funzione una o più volte e quel pezzo di codice non è necessario in nessun altro posto in modo da poter creare una funzione autonoma per esso.

Di seguito è riportato l'esempio di base di lambda e cosa succede in background.

Codice utente:

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

  endGame(4,5);

  return 0;

}

Come la compilazione lo espande:

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

così come puoi vedere, che tipo di overhead aggiunge quando lo usi. quindi non è una buona idea usarli ovunque. può essere utilizzato nei luoghi in cui sono applicabili.


sì, è letteralmente in movimento, lo definisci; usalo; e al termine dell'ambito della funzione genitore, la funzione lambda non è più attiva . Cosa succede se la funzione restituisce lambda al chiamante?
Nawaz,

1
Inoltre, non è il più veloce rispetto alla normale funzione. perché ha delle spese generali che devono essere gestite da lambda. Hai mai effettivamente eseguito un benchmark per supportare questa affermazione ? Al contrario, i modelli lambda + spesso producono il codice più veloce possibile.
Nawaz,

1

Un problema che risolve: codice più semplice di lambda per una chiamata nel costruttore che utilizza una funzione di parametro di output per inizializzare un membro const

Puoi inizializzare un membro const della tua classe, con una chiamata a una funzione che imposta il suo valore restituendo l'output come parametro di output.


Questo può anche essere fatto con una semplice funzione, che è anche ciò che la risposta accettata alla domanda a cui ti sei collegato dice di fare.
SirGuy,
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.