Espressioni lambda in C++

Lambda Expressions C



Perché l'espressione Lambda?

Considera la seguente affermazione:

intmioInt= 52;

Qui, myInt è un identificatore, un lvalue. 52 è un letterale, un valore. Oggi è possibile codificare una funzione in modo speciale e metterla nella posizione di 52. Tale funzione è chiamata espressione lambda. Considera anche il seguente breve programma:







#includere

usando spazio dei nomiore;

intfn(intattraverso)

{

intRispondere=attraverso+ 3;

RestituzioneRispondere;

}


intprincipale()

{

fn(5);



Restituzione 0;

}

Oggi è possibile codificare una funzione appositamente e metterla nella posizione dell'argomento 5, della chiamata di funzione, fn(5). Tale funzione è chiamata espressione lambda. L'espressione lambda (funzione) in quella posizione è un prvalue.



Qualsiasi letterale eccetto il letterale stringa è un prvalue. L'espressione lambda è un design di funzione speciale che si adatterebbe come un letterale nel codice. È una funzione anonima (senza nome). Questo articolo spiega la nuova espressione primaria C++, chiamata espressione lambda. La conoscenza di base del C++ è un requisito per comprendere questo articolo.



Contenuto dell'articolo

Illustrazione dell'espressione Lambda

Nel seguente programma, una funzione, che è un'espressione lambda, viene assegnata a una variabile:





#includere

usando spazio dei nomiore;

autofn= [](intfermare)

{

intRispondere=fermare+ 3;

RestituzioneRispondere;

};


intprincipale()

{

autovariabile=fn(2);

costo <<variabile<< ' ';


Restituzione 0;

}

L'uscita è:

5

Al di fuori della funzione main(), c'è la variabile fn. Il suo tipo è automatico. Auto in questa situazione significa che il tipo effettivo, ad esempio int o float, è determinato dall'operando destro dell'operatore di assegnazione (=). A destra dell'operatore di assegnazione c'è un'espressione lambda. Un'espressione lambda è una funzione senza il tipo restituito precedente. Notare l'uso e la posizione delle parentesi quadre, []. La funzione restituisce 5, un int, che determinerà il tipo per fn.



Nella funzione main(), c'è l'istruzione:

autovariabile=fn(2);

Ciò significa che fn al di fuori di main(), finisce per essere l'identificatore di una funzione. I suoi parametri impliciti sono quelli dell'espressione lambda. Il tipo di variabile è auto.

Nota che l'espressione lambda termina con un punto e virgola, proprio come la definizione di classe o struct, termina con un punto e virgola.

Nel seguente programma, una funzione, che è un'espressione lambda che restituisce il valore 5, è un argomento per un'altra funzione:

#includere

usando spazio dei nomiore;

vuotoaltro(intno1,int (*ptr)(int))

{

intno2= (*ptr)(2);

costo <<no1<< '' <<no2<< ' ';

}


intprincipale()

{

altro(4,[](intfermare)

{

intRispondere=fermare+ 3;

RestituzioneRispondere;

});


Restituzione 0;
}

L'uscita è:

Quattro cinque

Ci sono due funzioni qui, l'espressione lambda e la funzione otherfn(). L'espressione lambda è il secondo argomento di otherfn(), chiamato in main(). Nota che la funzione lambda (espressione) non termina con un punto e virgola in questa chiamata perché, qui, è un argomento (non una funzione autonoma).

Il parametro della funzione lambda nella definizione della funzione otherfn() è un puntatore a una funzione. Il puntatore ha il nome, ptr. Il nome, ptr, viene utilizzato nella definizione otherfn() per chiamare la funzione lambda.

La dichiarazione,

intno2= (*ptr)(2);

Nella definizione otherfn(), chiama la funzione lambda con un argomento pari a 2. Il valore restituito dalla chiamata, '(*ptr)(2)' dalla funzione lambda, è assegnato a no2.

Il programma sopra mostra anche come la funzione lambda può essere utilizzata nello schema della funzione di callback C++.

Parti dell'espressione Lambda

Le parti di una tipica funzione lambda sono le seguenti:

[] () {}
  • [] è la clausola di cattura. Può avere oggetti.
  • () è per l'elenco dei parametri.
  • {} è per il corpo della funzione. Se la funzione è isolata, dovrebbe terminare con un punto e virgola.

cattura

La definizione della funzione lambda può essere assegnata a una variabile o utilizzata come argomento per una chiamata di funzione diversa. La definizione per tale chiamata di funzione dovrebbe avere come parametro, un puntatore a una funzione, corrispondente alla definizione della funzione lambda.

La definizione della funzione lambda è diversa dalla normale definizione della funzione. Può essere assegnato a una variabile nell'ambito globale; questa funzione assegnata alla variabile può essere codificata anche all'interno di un'altra funzione. Quando viene assegnato a una variabile di ambito globale, il suo corpo può vedere altre variabili nell'ambito globale. Quando viene assegnata a una variabile all'interno di una definizione di funzione normale, il suo corpo può vedere altre variabili nell'ambito della funzione solo con l'aiuto della clausola di cattura, [].

La clausola di cattura [], nota anche come lambda-introducer, consente di inviare variabili dall'ambito (funzione) circostante al corpo della funzione dell'espressione lambda. Si dice che il corpo della funzione dell'espressione lambda catturi la variabile quando riceve l'oggetto. Senza la clausola di cattura [], non è possibile inviare una variabile dall'ambito circostante al corpo della funzione dell'espressione lambda. Il seguente programma illustra questo, con l'ambito della funzione main(), come ambito circostante:

#includere

usando spazio dei nomiore;

intprincipale()

{

intID= 5;


autofn= [ID]()

{

costo <<ID<< ' ';

};

fn();


Restituzione 0;

}

L'uscita è 5 . Senza il nome, id, all'interno di [], l'espressione lambda non avrebbe visto l'id della variabile dell'ambito della funzione main().

Acquisizione per riferimento

L'esempio precedente della clausola di acquisizione è l'acquisizione per valore (vedere i dettagli di seguito). Nell'acquisizione per riferimento, la posizione (memorizzazione) della variabile, ad esempio id sopra, dell'ambito circostante, è resa disponibile all'interno del corpo della funzione lambda. Quindi, cambiando il valore della variabile all'interno del corpo della funzione lambda cambierà il valore di quella stessa variabile nell'ambito circostante. Ogni variabile ripetuta nella clausola di cattura è preceduta dalla e commerciale (&) per ottenere ciò. Il seguente programma lo illustra:

#includere

usando spazio dei nomiore;

intprincipale()

{

intID= 5; galleggianteft= 2.3; charch= 'A';

autofn= [&ID,&piedi,&ch]()

{

ID= 6;ft= 3.4;ch= 'B';

};

fn();

costo <<ID<< ',' <<ft<< ',' <<ch<< ' ';

Restituzione 0;

}

L'uscita è:

6, 3.4, SI

Confermando che i nomi delle variabili all'interno del corpo della funzione dell'espressione lambda sono per le stesse variabili al di fuori dell'espressione lambda.

Cattura per valore

Nell'acquisizione per valore, viene resa disponibile una copia della posizione della variabile, dell'ambito circostante, all'interno del corpo della funzione lambda. Sebbene la variabile all'interno del corpo della funzione lambda sia una copia, al momento il suo valore non può essere modificato all'interno del corpo. Per ottenere la cattura per valore, ogni variabile ripetuta nella clausola di cattura non è preceduta da nulla. Il seguente programma lo illustra:

#includere

usando spazio dei nomiore;

intprincipale()

{

intID= 5; galleggianteft= 2.3; charch= 'A';

autofn= [id, ft, ch]()

{

//id = 6; piedi = 3,4; ch = 'B';

costo <<ID<< ',' <<ft<< ',' <<ch<< ' ';

};

fn();

ID= 6;ft= 3.4;ch= 'B';

costo <<ID<< ',' <<ft<< ',' <<ch<< ' ';

Restituzione 0;

}

L'uscita è:

5, 2.3, LA

6, 3.4, SI

Se l'indicatore di commento viene rimosso, il programma non verrà compilato. Il compilatore invierà un messaggio di errore che le variabili all'interno della definizione del corpo della funzione dell'espressione lambda non possono essere modificate. Sebbene le variabili non possano essere modificate all'interno della funzione lambda, possono essere modificate all'esterno della funzione lambda, come mostra l'output del programma sopra.

Catture di mixaggio

L'acquisizione per riferimento e l'acquisizione per valore possono essere combinate, come mostra il seguente programma:

#includere

usando spazio dei nomiore;

intprincipale()

{

intID= 5; galleggianteft= 2.3; charch= 'A'; boolblu= vero;


autofn= [ID, piedi,&ch,&blu]()

{

ch= 'B';blu= falso;

costo <<ID<< ',' <<ft<< ',' <<ch<< ',' <<blu<< ' ';

};

fn();


Restituzione 0;

}

L'uscita è:

5, 2.3, SI, 0

Quando tutti catturati, sono per riferimento:

Se tutte le variabili da catturare vengono catturate per riferimento, sarà sufficiente un solo & nella clausola di cattura. Il seguente programma lo illustra:

#includere

usando spazio dei nomiore;

intprincipale()

{

intID= 5; galleggianteft= 2.3; charch= 'A'; boolblu= vero;


autofn= [&]()

{

ID= 6;ft= 3.4;ch= 'B';blu= falso;

};

fn();

costo <<ID<< ',' <<ft<< ',' <<ch<< ',' <<blu<< ' ';


Restituzione 0;

}

L'uscita è:

6, 3.4, SI, 0

Se alcune variabili devono essere catturate per riferimento e altre per valore, allora uno & rappresenterà tutti i riferimenti e il resto non sarà preceduto da nulla, come mostra il seguente programma:

usando spazio dei nomiore;

intprincipale()

{

intID= 5; galleggianteft= 2.3; charch= 'A'; boolblu= vero;


autofn= [&, id, ft]()

{

ch= 'B';blu= falso;

costo <<ID<< ',' <<ft<< ',' <<ch<< ',' <<blu<< ' ';

};

fn();


Restituzione 0;

}

L'uscita è:

5, 2.3, SI, 0

Nota che & da solo (cioè & non seguito da un identificatore) deve essere il primo carattere nella clausola di cattura.

Quando tutti catturati, sono per valore:

Se tutte le variabili da catturare devono essere catturate per valore, allora solo uno = sarà sufficiente nella clausola di cattura. Il seguente programma lo illustra:

#includere

usando spazio dei nomiore;

intprincipale()
{

intID= 5; galleggianteft= 2.3; charch= 'A'; boolblu= vero;


autofn= [=]()

{

costo <<ID<< ',' <<ft<< ',' <<ch<< ',' <<blu<< ' ';

};

fn();


Restituzione 0;


}

L'uscita è:

5, 2.3, LA, 1

Nota : = è di sola lettura, al momento.

Se alcune variabili devono essere catturate per valore e altre per riferimento, allora uno = rappresenterà tutte le variabili copiate di sola lettura e il resto avrà ciascuno &, come mostra il seguente programma:

#includere

usando spazio dei nomiore;

intprincipale()

{

intID= 5; galleggianteft= 2.3; charch= 'A'; boolblu= vero;


autofn= [=,&ch,&blu]()

{

ch= 'B';blu= falso;

costo <<ID<< ',' <<ft<< ',' <<ch<< ',' <<blu<< ' ';

};

fn();


Restituzione 0;

}

L'uscita è:

5, 2.3, SI, 0

Nota che = da solo deve essere il primo carattere nella clausola di cattura.

Schema di funzione di callback classico con espressione lambda

Il seguente programma mostra come è possibile eseguire uno schema di funzione di callback classico con l'espressione lambda:

#includere

usando spazio dei nomiore;

char *produzione;


autocba= [](charfuori[])

{

produzione=fuori;

};



vuotomainFunc(charingresso[],vuoto (*per)(char[]))

{

(*per)(ingresso);

costo<<'per funzione principale'<<' ';

}


vuotofn()

{

costo<<'Ora'<<' ';

}


intprincipale()

{

charingresso[] = 'per la funzione di richiamata';

mainFunc(ingresso, cba);

fn();

costo<<produzione<<' ';



Restituzione 0;

}

L'uscita è:

per la funzione principale

Ora

per la funzione di richiamata

Ricorda che quando una definizione di espressione lambda viene assegnata a una variabile nell'ambito globale, il suo corpo funzione può vedere le variabili globali senza utilizzare la clausola di cattura.

Il tipo trailing-return

Il tipo restituito di un'espressione lambda è auto, il che significa che il compilatore determina il tipo restituito dall'espressione restituita (se presente). Se il programmatore vuole davvero indicare il tipo di ritorno, allora lo farà come nel seguente programma:

#includere

usando spazio dei nomiore;

autofn= [](intfermare) -> int

{

intRispondere=fermare+ 3;

RestituzioneRispondere;

};


intprincipale()

{

autovariabile=fn(2);

costo <<variabile<< ' ';


Restituzione 0;

}

L'output è 5. Dopo l'elenco dei parametri, viene digitato l'operatore freccia. Questo è seguito dal tipo restituito (int in questo caso).

Chiusura

Considera il seguente segmento di codice:

strutturaCla

{

intID= 5;

charch= 'a';

}obj1, obj2;

Qui Cla è il nome della classe struct. Obj1 e obj2 sono due oggetti che verranno istanziati dalla classe struct. L'espressione lambda è simile nell'implementazione. La definizione della funzione lambda è una specie di classe. Quando viene chiamata (invocata) la funzione lambda, viene istanziata un oggetto dalla sua definizione. Questo oggetto è chiamato chiusura. È la chiusura che fa il lavoro che ci si aspetta che la lambda faccia.

Tuttavia, codificando l'espressione lambda come la struttura sopra, obj1 e obj2 saranno sostituiti dagli argomenti dei parametri corrispondenti. Il seguente programma lo illustra:

#includere

usando spazio dei nomiore;

autofn= [](intparametro1,intparam2)

{

intRispondere=param1+param2;

RestituzioneRispondere;

} (2,3);


intprincipale()

{

autodove=fn;

costo <<dove<< ' ';


Restituzione 0;

}

L'output è 5. Gli argomenti sono 2 e 3 tra parentesi. Si noti che la chiamata alla funzione dell'espressione lambda, fn, non accetta alcun argomento poiché gli argomenti sono già stati codificati alla fine della definizione della funzione lambda.

Conclusione

L'espressione lambda è una funzione anonima. È diviso in due parti: classe e oggetto. La sua definizione è una specie di classe. Quando viene chiamata l'espressione, dalla definizione viene formato un oggetto. Questo oggetto è chiamato chiusura. È la chiusura che fa il lavoro che ci si aspetta che la lambda faccia.

Affinché l'espressione lambda riceva una variabile da un ambito di funzione esterno, è necessaria una clausola di acquisizione non vuota nel corpo della funzione.