Ricerca…


Sintassi

  • [ default-capture , capture-list ] ( argomento-elenco ) attributi mutable throw-specification -> return-type { lambda-body } // Order of lambda specificatori e attributi.
  • [ capture-list ] ( argument-list ) { lambda-body } // Definizione lambda comune.
  • [=] ( argument-list ) { lambda-body } // Cattura tutte le variabili locali necessarie per valore.
  • [&] ( argument-list ) { lambda-body } // Cattura tutte le variabili locali necessarie per riferimento.
  • [ capture-list ] { lambda-body } // L'elenco degli argomenti e gli specificatori possono essere omessi.

Parametri

Parametro Dettagli
default-capture Specifica come vengono catturate tutte le variabili non elencate. Può essere = (acquisizione per valore) o & (acquisizione per riferimento). Se omesso, le variabili non elencate sono inaccessibili all'interno del corpo lambda . L' acquisizione di default deve precedere la lista di cattura .
capture-list Specifica in che modo le variabili locali sono rese accessibili all'interno del corpo lambda . Le variabili senza prefisso vengono catturate dal valore. Le variabili con prefisso & vengono acquisite per riferimento. All'interno di un metodo di classe, this può essere usato per rendere tutti i suoi membri accessibili per riferimento. Le variabili non elencate sono inaccessibili, a meno che l'elenco non sia preceduto da un'acquisizione predefinita .
argument-list Specifica gli argomenti della funzione lambda.
mutevole (facoltativo) Normalmente le variabili acquisite dal valore sono const . La specifica di mutable li rende non-const. Le modifiche a tali variabili vengono mantenute tra le chiamate.
gettare-specifica (facoltativo) Specifica il comportamento di lancio delle eccezioni della funzione lambda. Ad esempio: noexcept o throw(std::exception) .
attributi (opzionale) Qualsiasi attributo per la funzione lambda. Ad esempio, se il corpo lambda genera sempre un'eccezione, allora [[noreturn]] può essere usato.
-> tipo di ritorno (opzionale) Specifica il tipo di ritorno della funzione lambda. Obbligatorio quando il tipo di ritorno non può essere determinato dal compilatore.
lambda-body Un blocco di codice contenente l'implementazione della funzione lambda.

Osservazioni

C ++ 17 (la bozza attuale) introduce constexpr , fondamentalmente lambda che può essere valutato in fase di compilazione. Un lambda è automaticamente constexpr se soddisfa i requisiti di constexpr , ma è anche possibile specificarlo usando la parola chiave constexpr :

//Explicitly define this lambdas as constexpr
[]() constexpr {
    //Do stuff
}

Cos'è un'espressione lambda?

Un'espressione lambda fornisce un modo conciso per creare oggetti funzione semplici. Un'espressione lambda è un prvalore il cui oggetto risultato è chiamato oggetto chiusura , che si comporta come un oggetto funzione.

Il nome "espressione lambda" deriva dal lambda calcolo , che è un formalismo matematico inventato negli anni '30 dalla Chiesa di Alonzo per indagare sulle questioni relative alla logica e alla computabilità. Il calcolo Lambda ha costituito la base di LISP , un linguaggio di programmazione funzionale. Rispetto al lambda calcolo e al LISP, le espressioni lambda in C ++ condividono le proprietà di essere senza nome e di catturare variabili dal contesto circostante, ma non hanno la capacità di operare e restituire funzioni.

Un'espressione lambda viene spesso utilizzata come argomento per le funzioni che accettano un oggetto chiamabile. Ciò può essere più semplice della creazione di una funzione con nome, che verrebbe utilizzata solo quando passata come argomento. In tali casi, le espressioni lambda sono generalmente preferite perché consentono di definire gli oggetti funzione in linea.

Un lambda consiste tipicamente di tre parti: una lista di cattura [] , un elenco di parametri facoltativo () e un corpo {} , che possono essere tutti vuoti:

[](){}                // An empty lambda, which does and returns nothing

Elenco di acquisizione

[] è la lista di cattura . Per impostazione predefinita, non è possibile accedere alle variabili dello scope che racchiude un lambda. Catturare una variabile lo rende accessibile all'interno della lambda, sia come copia che come riferimento . Le variabili catturate diventano una parte del lambda; al contrario degli argomenti della funzione, non devono essere passati quando si chiama il lambda.

int a = 0;                       // Define an integer variable
auto f = []()   { return a*9; }; // Error: 'a' cannot be accessed
auto f = [a]()  { return a*9; }; // OK, 'a' is "captured" by value
auto f = [&a]() { return a++; }; // OK, 'a' is "captured" by reference
                                 //      Note: It is the responsibility of the programmer
                                 //      to ensure that a is not destroyed before the
                                 //      lambda is called.
auto b = f();                    // Call the lambda function. a is taken from the capture list and not passed here.

Lista dei parametri

() è la lista dei parametri , che è quasi la stessa di quella delle normali funzioni. Se il lambda non accetta argomenti, queste parentesi possono essere omesse (eccetto se è necessario dichiarare il mutable lambda). Questi due lambda sono equivalenti:

auto call_foo  = [x](){ x.foo(); };
auto call_foo2 = [x]{ x.foo(); };
C ++ 14

L'elenco dei parametri può utilizzare il tipo di segnaposto auto posto dei tipi effettivi. In questo modo, questo argomento si comporta come un parametro di modello di un modello di funzione. I seguenti lambda sono equivalenti quando si desidera ordinare un vettore in codice generico:

auto sort_cpp11 = [](std::vector<T>::const_reference lhs, std::vector<T>::const_reference rhs) { return lhs < rhs; }; 
auto sort_cpp14 = [](const auto &lhs, const auto &rhs) { return lhs < rhs; }; 

Corpo della funzione

{} è il corpo , che è lo stesso delle normali funzioni.


Chiamando un lambda

L'oggetto risultato di un'espressione lambda è una chiusura , che può essere chiamata usando l' operator() (come con altri oggetti funzione):

int multiplier = 5;
auto timesFive = [multiplier](int a) { return a * multiplier; }; 
std::out << timesFive(2); // Prints 10

multiplier = 15;
std::out << timesFive(2); // Still prints 2*5 == 10

Tipo di reso

Per impostazione predefinita, viene dedotto il tipo di ritorno di un'espressione lambda.

[](){ return true; };

In questo caso il tipo di bool è bool .

Puoi anche specificare manualmente il tipo di ritorno usando la seguente sintassi:

[]() -> bool { return true; };

Mutevole Lambda

Gli oggetti catturati dal valore nel lambda sono di default immutabili. Questo perché l' operator() dell'oggetto di chiusura generato è const per impostazione predefinita.

auto func = [c = 0](){++c; std::cout << c;};  // fails to compile because ++c
                                              // tries to mutate the state of
                                              // the lambda.

La modifica può essere consentita usando la parola chiave mutable , che rende non- const l' operator() dell'oggetto più vicino operator() :

auto func = [c = 0]() mutable {++c; std::cout << c;};

Se usato insieme al tipo restituito, il mutable viene prima di esso.

auto func = [c = 0]() mutable -> int {++c; std::cout << c; return c;};

Un esempio per illustrare l'utilità di lambda

Prima del C ++ 11:

C ++ 11
// Generic functor used for comparison
struct islessthan
{
    islessthan(int threshold) : _threshold(threshold) {}

    bool operator()(int value) const
    {
        return value < _threshold;
    }
private:
    int _threshold;
};

// Declare a vector
const int arr[] = { 1, 2, 3, 4, 5 };
std::vector<int> vec(arr, arr+5);

// Find a number that's less than a given input (assume this would have been function input)
int threshold = 10;
std::vector<int>::iterator it = std::find_if(vec.begin(), vec.end(), islessthan(threshold));

Dal momento che C ++ 11:

C ++ 11
// Declare a vector
std::vector<int> vec{ 1, 2, 3, 4, 5 };

// Find a number that's less than a given input (assume this would have been function input)
int threshold = 10;
auto it = std::find_if(vec.begin(), vec.end(), [threshold](int value) { return value < threshold; });

Specifica del tipo di reso

Per lambdas con una sola istruzione return, o più istruzioni return le cui espressioni sono dello stesso tipo, il compilatore può dedurre il tipo restituito:

// Returns bool, because "value > 10" is a comparison which yields a Boolean result
auto l = [](int value) {
    return value > 10;
}

Per lambda con più dichiarazioni di ritorno di tipi diversi , il compilatore non può dedurre il tipo di reso:

// error: return types must match if lambda has unspecified return type
auto l = [](int value) {
    if (value < 10) {
        return 1;
    } else {
        return 1.5;
    }
};

In questo caso devi specificare esplicitamente il tipo di reso:

// The return type is specified explicitly as 'double'
auto l = [](int value) -> double {
    if (value < 10) {
        return 1;
    } else {
        return 1.5;
    }
};

Le regole per questo corrispondono alle regole per auto deduzione del tipo auto . Lambdas senza tipi di ritorno specificati in modo esplicito non restituisce mai riferimenti, quindi se si desidera un tipo di riferimento deve essere specificato esplicitamente:

auto copy = [](X& x) { return x; };       // 'copy' returns an X, so copies its input
auto ref  = [](X& x) -> X& { return x; }; // 'ref' returns an X&, no copy

Cattura in base al valore

Se si specifica il nome della variabile nella lista di cattura, lambda la catturerà per valore. Ciò significa che il tipo di chiusura generato per il lambda memorizza una copia della variabile. Ciò richiede anche che il tipo della variabile sia copy-constructible :

int a = 0;

[a]() {
    return a;   // Ok, 'a' is captured by value
};
C ++ 14
auto p = std::unique_ptr<T>(...);

[p]() {         // Compile error; `unique_ptr` is not copy-constructible
    return p->createWidget(); 
};

Da C ++ 14 in poi, è possibile inizializzare le variabili sul posto. Ciò consente di spostare solo i tipi da catturare nella lambda.

C ++ 14
auto p = std::make_unique<T>(...);

[p = std::move(p)]() {
    return p->createWidget(); 
};

Anche se un lambda cattura le variabili in base al valore quando sono date dal loro nome, tali variabili non possono essere modificate all'interno del corpo lambda di default. Questo perché il tipo di chiusura inserisce il corpo lambda in una dichiarazione di operator() const .

Il const applica agli accessi alle variabili membro del tipo di chiusura e alle variabili catturate che sono membri della chiusura (tutte le apparenze in contrario):

int a = 0;

[a]() {
    a = 2;      // Illegal, 'a' is accessed via `const`

    decltype(a) a1 = 1; 
    a1 = 2; // valid: variable 'a1' is not const
};

Per rimuovere il const , devi specificare la parola chiave mutable sul lambda:

int a = 0;

[a]() mutable {
    a = 2;      // OK, 'a' can be modified
    return a;
};

Poiché a stato catturato dal valore, qualsiasi modifica eseguita chiamando lambda non influirà su a . Il valore di a stato copiato nella lambda quando è stato costruito, in modo copia del lambda di a separato dall'esterno a variabile.

int a = 5 ; 
auto plus5Val = [a] (void) { return a + 5 ; } ; 
auto plus5Ref = [&a] (void) {return a + 5 ; } ; 

a = 7 ; 
std::cout << a << ", value " << plus5Val() << ", reference " << plus5Ref() ;
// The result will be "7, value 10, reference 12"

Acquisizione generalizzata

C ++ 14

Lambda può catturare espressioni, piuttosto che semplici variabili. Ciò consente a lambda di memorizzare i tipi di spostamento:

auto p = std::make_unique<T>(...);

auto lamb = [p = std::move(p)]() //Overrides capture-by-value of `p`.
{
  p->SomeFunc();
};

Ciò sposta la variabile p esterna nella variabile di cattura lambda, anche chiamata p . lamb ora possiede la memoria allocata da make_unique . Poiché la chiusura contiene un tipo che non può essere copiato, ciò significa che l' lamb è esso stesso non copiabile. Ma può essere spostato:

auto lamb_copy = lamb; //Illegal
auto lamb_move = std::move(lamb); //legal.

Ora lamb_move possiede la memoria.


Nota che std::function<> richiede che i valori memorizzati siano copiabili. Puoi scrivere la tua std::function richiede solo lo spostamento , oppure puoi semplicemente inserire il lambda in un wrapper shared_ptr :

auto shared_lambda = [](auto&& f){
  return [spf = std::make_shared<std::decay_t<decltype(f)>>(decltype(f)(f))]
  (auto&&...args)->decltype(auto) {
    return (*spf)(decltype(args)(args)...);
  };
};
auto lamb_shared = shared_lambda(std::move(lamb_move));

prende il nostro lambda di sola mossa e carica il suo stato in un puntatore condiviso, quindi restituisce un lambda che può essere copiato e quindi memorizzato in una std::function o simile.


L'acquisizione generalizzata utilizza auto deduzione del tipo auto per il tipo di variabile. Dichiarerà queste acquisizioni come valori per impostazione predefinita, ma possono anche essere riferimenti:

int a = 0;

auto lamb = [&v = a](int add) //Note that `a` and `v` have different names
{
  v += add; //Modifies `a`
};

lamb(20); //`a` becomes 20.

Generalizzare l'acquisizione non ha bisogno di catturare una variabile esterna. Può catturare un'espressione arbitraria:

auto lamb = [p = std::make_unique<T>(...)]()
{
    p->SomeFunc();
}

Questo è utile per dare a lambda valori arbitrari che possono contenere e potenzialmente modificare, senza doverli dichiarare esternamente al lambda. Naturalmente, ciò è utile solo se non si intende accedere a tali variabili dopo che lambda ha completato il proprio lavoro.

Cattura per riferimento

Se precedi il nome di una variabile locale con un & , allora la variabile verrà catturata per riferimento. Concettualmente, ciò significa che il tipo di chiusura di lambda avrà una variabile di riferimento, inizializzata come riferimento alla variabile corrispondente al di fuori dell'ambito del lambda. Qualsiasi utilizzo della variabile nel corpo lambda farà riferimento alla variabile originale:

// Declare variable 'a'
int a = 0;

// Declare a lambda which captures 'a' by reference
auto set = [&a]() {
    a = 1;
};

set();
assert(a == 1);

La parola chiave mutable non è necessaria, perché a stessa non è const .

Naturalmente, catturare per riferimento significa che il lambda non deve sfuggire allo scopo delle variabili che cattura. Quindi puoi chiamare funzioni che svolgono una funzione, ma non devi chiamare una funzione che memorizzerà il lambda oltre la portata dei tuoi riferimenti. E tu non devi restituire il lambda.

Cattura predefinita

Per impostazione predefinita, non è possibile accedere alle variabili locali che non sono esplicitamente specificate nell'elenco di cattura dall'interno del corpo lambda. Tuttavia, è possibile acquisire implicitamente variabili nominate dal corpo lambda:

int a = 1;
int b = 2;

// Default capture by value
[=]() { return a + b; }; // OK; a and b are captured by value

// Default capture by reference
[&]() { return a + b; }; // OK; a and b are captured by reference

L'acquisizione esplicita può ancora essere eseguita insieme all'acquisizione implicita di default. La definizione di acquisizione esplicita sovrascriverà l'acquisizione predefinita:

int a = 0;
int b = 1;

[=, &b]() {
    a = 2; // Illegal; 'a' is capture by value, and lambda is not 'mutable'
    b = 2; // OK; 'b' is captured by reference
};

Lambda generico

c ++ 14

Le funzioni Lambda possono assumere argomenti di tipo arbitrario. Ciò consente a un lambda di essere più generico:

auto twice = [](auto x){ return x+x; };

int i = twice(2); // i == 4
std::string s = twice("hello"); // s == "hellohello"

Questo è implementato in C ++ facendo in modo che l' operator() del tipo di chiusura operator() sovraccarichi una funzione modello. Il seguente tipo ha un comportamento equivalente alla chiusura lambda sopra:

struct _unique_lambda_type
{
  template<typename T>
  auto operator() (T x) const {return x + x;}
};

Non tutti i parametri in un lambda generico devono essere generici:

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

Qui, x è dedotto in base al primo argomento di funzione, mentre y sarà sempre int .

I lambda generici possono prendere argomenti come riferimento, usando le solite regole per auto e & . Se un parametro generico è preso come auto&& , questo è un riferimento di inoltro all'argomento passato e non un riferimento di rvalue :

auto lamb1 = [](int &&x) {return x + 5;};
auto lamb2 = [](auto &&x) {return x + 5;};
int x = 10;
lamb1(x); // Illegal; must use `std::move(x)` for `int&&` parameters.
lamb2(x); // Legal; the type of `x` is deduced as `int&`.

Le funzioni Lambda possono essere variadiche e inoltrare perfettamente i loro argomenti:

auto lam = [](auto&&... args){return f(std::forward<decltype(args)>(args)...);};

o:

auto lam = [](auto&&... args){return f(decltype(args)(args)...);};

che funziona solo "correttamente" con variabili di tipo auto&& .

Un motivo valido per utilizzare lambda generico è per la sintassi in visita.

boost::variant<int, double> value;
apply_visitor(value, [&](auto&& e){
  std::cout << e;
});

Qui stiamo visitando in modo polimorfico; ma in altri contesti, i nomi del tipo che stiamo passando non sono interessanti:

mutex_wrapped<std::ostream&> os = std::cout;
os.write([&](auto&& os){
  os << "hello world\n";
});

Ripetendo il tipo di std::ostream& is noise qui; sarebbe come dover menzionare il tipo di variabile ogni volta che la usi. Qui stiamo creando un visitatore, ma non uno polimorfico; auto è usato per lo stesso motivo per cui si può usare auto in un ciclo for(:) .

Conversione al puntatore della funzione

Se la lista di cattura di un lambda è vuota, allora lambda ha una conversione implicita in un puntatore a funzione che prende gli stessi argomenti e restituisce lo stesso tipo di ritorno:

auto sorter = [](int lhs, int rhs) -> bool {return lhs < rhs;};

using func_ptr = bool(*)(int, int);
func_ptr sorter_func = sorter; // implicit conversion

Tale conversione può anche essere applicata utilizzando un operatore unario più:

func_ptr sorter_func2 = +sorter; // enforce implicit conversion

La chiamata a questo puntatore di funzione si comporta esattamente come invocando operator() sul lambda. Questo puntatore a funzione non è in alcun modo dipendente dall'esistenza della chiusura lambda sorgente. Potrebbe quindi sopravvivere alla chiusura lambda.

Questa funzione è utile principalmente per l'utilizzo di lambda con API che trattano i puntatori di funzione, piuttosto che gli oggetti funzione C ++.

C ++ 14

La conversione a un puntatore di funzione è anche possibile per lambda generico con una lista di cattura vuota. Se necessario, verrà utilizzata la deduzione degli argomenti del modello per selezionare la specializzazione corretta.

auto sorter = [](auto lhs, auto rhs) { return lhs < rhs; };
using func_ptr = bool(*)(int, int);
func_ptr sorter_func = sorter;  // deduces int, int
// note however that the following is ambiguous
// func_ptr sorter_func2 = +sorter;

Classe lambda e cattura di questo

Un'espressione lambda valutata in una funzione membro della classe è implicitamente un amico di quella classe:

class Foo
{
private:
    int i;
    
public:
    Foo(int val) : i(val) {}
    
    // definition of a member function
    void Test()
    {
        auto lamb = [](Foo &foo, int val)
        {
            // modification of a private member variable
            foo.i = val;
        };
        
        // lamb is allowed to access a private member, because it is a friend of Foo
        lamb(*this, 30);
    }
};

Tale lambda non è solo un amico di quella classe, ha lo stesso accesso della classe dichiarata all'interno.

Lambda possono catturare this puntatore che rappresenta l'istanza oggetto la funzione esterno era chiamato. Questo viene fatto aggiungendo this alla lista di cattura:

class Foo
{
private:
    int i;
    
public:
    Foo(int val) : i(val) {}
    
    void Test()
    {
        // capture the this pointer by value
        auto lamb = [this](int val)
        {
            i = val;
        };
        
        lamb(30);
    }
};

Quando this viene catturato, il lambda può usare i nomi dei membri della sua classe contenente come se fosse nella sua classe contenente. Quindi un implicito this-> è applicato a tali membri.

Si noti che this viene catturato dal valore, ma non dal valore del tipo. Viene catturato dal valore di this , che è un puntatore . In quanto tale, il lambda non possiede this . Se il lambda out vive la vita dell'oggetto che lo ha creato, il lambda può diventare non valido.

Questo significa anche che la lambda può modificare this senza essere dichiarato mutable . È il puntatore che è const , non l'oggetto puntato. Cioè, a meno che la funzione membro esterno fosse di per sé una funzione const .

Inoltre, tieni presente che le clausole di acquisizione predefinite, sia [=] che [&] , cattureranno anche this implicitamente. E entrambi lo catturano per il valore del puntatore. In effetti, si tratta di un errore di specificare this nella lista di cattura quando viene dato un valore predefinito.

C ++ 17

Lambdas può catturare una copia di this oggetto, creata nel momento in cui viene creata la lambda. Questo viene fatto aggiungendo *this alla lista di cattura:

class Foo
{
private:
    int i;
    
public:
    Foo(int val) : i(val) {}
    
    void Test()
    {
        // capture a copy of the object given by the this pointer
        auto lamb = [*this](int val) mutable
        {
            i = val;
        };
        
        lamb(30); // does not change this->i
    }
};

Portare funzioni lambda a C ++ 03 usando i funtori

Le funzioni Lambda in C ++ sono zucchero sintattico che fornisce una sintassi molto concisa per scrivere i funtori . Come tale, è possibile ottenere funzionalità equivalenti in C ++ 03 (anche se molto più prolisso) convertendo la funzione lambda in un functor:

// Some dummy types:
struct T1 {int dummy;};
struct T2 {int dummy;};
struct R {int dummy;};

// Code using a lambda function (requires C++11)
R use_lambda(T1 val, T2 ref) {
  // Use auto because the type of the lambda is unknown.
  auto lambda = [val, &ref](int arg1, int arg2) -> R {
    /* lambda-body */
    return R();
  };
  return lambda(12, 27);
}

// The functor class (valid C++03)
// Similar to what the compiler generates for the lambda function.
class Functor {
  // Capture list.
  T1 val;
  T2& ref;

public:
  // Constructor
  inline Functor(T1 val, T2& ref) : val(val), ref(ref) {}

  // Functor body
  R operator()(int arg1, int arg2) const {
    /* lambda-body */
    return R();
  }
};

// Equivalent to use_lambda, but uses a functor (valid C++03).
R use_functor(T1 val, T2 ref) {
  Functor functor(val, ref);
  return functor(12, 27);
}

// Make this a self-contained example.
int main() {
  T1 t1;
  T2 t2;
  use_functor(t1,t2);
  use_lambda(t1,t2);
  return 0;
}

Se la funzione lambda è mutable rendi il call-operator del funtore non const, cioè:

R operator()(int arg1, int arg2) /*non-const*/ {
  /* lambda-body */
  return R();
}

Lambda ricorsivo

Diciamo che desideriamo scrivere Ecdid's gcd() come un lambda. Come una funzione, è:

int gcd(int a, int b) {
    return b == 0 ? a : gcd(b, a%b);
}

Ma un lambda non può essere ricorsivo, non ha modo di invocare se stesso. Un lambda ha nome e utilizzando this all'interno del corpo di un lambda si riferisce a un catturato this (assumendo che il lambda viene creato nel corpo di una funzione membro, altrimenti è un errore). Quindi come risolviamo questo problema?

Usa la std::function

Possiamo avere un lambda per catturare un riferimento a una std::function non ancora costruita:

std::function<int(int, int)> gcd = [&](int a, int b){
    return b == 0 ? a : gcd(b, a%b);
};

Funziona, ma dovrebbe essere usato con parsimonia. È lento (stiamo usando la cancellazione di tipo ora invece di una chiamata di funzione diretta), è fragile (copiare gcd o restituire gcd si interromperà poiché lambda si riferisce all'oggetto originale) e non funzionerà con lambdas generici.

Usando due puntatori intelligenti:

auto gcd_self = std::make_shared<std::unique_ptr< std::function<int(int, int)> >>();
*gcd_self = std::make_unique<std::function<int(int, int)>>(
  [gcd_self](int a, int b){
    return b == 0 ? a : (**gcd_self)(b, a%b);
  };
};

Ciò aggiunge un sacco di riferimenti indiretti (che sono generali), ma può essere copiato / restituito e tutte le copie condividono lo stato. Ti consente di restituire la lambda, ed è altrimenti meno fragile della soluzione di cui sopra.

Usa un combinatore a Y

Con l'aiuto di una breve struttura di utilità, possiamo risolvere tutti questi problemi:

template <class F>
struct y_combinator {
    F f; // the lambda will be stored here

    // a forwarding operator():
    template <class... Args>
    decltype(auto) operator()(Args&&... args) const {
        // we pass ourselves to f, then the arguments.
        // the lambda should take the first argument as `auto&& recurse` or similar.
        return f(*this, std::forward<Args>(args)...);
    }
};
// helper function that deduces the type of the lambda:
template <class F>
y_combinator<std::decay_t<F>> make_y_combinator(F&& f) {
    return {std::forward<F>(f)};
}
// (Be aware that in C++17 we can do better than a `make_` function)

possiamo implementare il nostro gcd come:

auto gcd = make_y_combinator(
  [](auto&& gcd, int a, int b){
    return b == 0 ? a : gcd(b, a%b);
  }
);

y_combinator è un concetto y_combinator dal calcolo lambda che ti consente di avere una ricorsione senza essere in grado di y_combinator fino a quando non sei definito. Questo è esattamente il problema dei lambda.

Crei un lambda che prende "recurse" come primo argomento. Quando si desidera recurse, si passano gli argomenti per recurse.

y_combinator restituisce quindi un oggetto funzione che chiama tale funzione con i suoi argomenti, ma con un oggetto " y_combinator " adatto (ovvero lo y_combinator stesso) come primo argomento. y_combinator il resto degli argomenti che chiamate y_combinator con il lambda.

In breve:

auto foo = make_y_combinator( [&](auto&& recurse, some arguments) {
  // write body that processes some arguments
  // when you want to recurse, call recurse(some other arguments)
});

e hai ricorsione in una lambda senza restrizioni gravi o sovraccarico significativo.

Utilizzo di lambda per il disimballaggio del pacchetto di parametri inline

C ++ 14

Tradizionalmente, il disimballaggio del pacchetto di parametri richiede la scrittura di una funzione di supporto per ogni volta che si desidera eseguirla.

In questo esempio di giocattolo:

template<std::size_t...Is>
void print_indexes( std::index_sequence<Is...> ) {
  using discard=int[];
  (void)discard{0,((void)(
    std::cout << Is << '\n' // here Is is a compile-time constant.
  ),0)...};
}
template<std::size_t I>
void print_indexes_upto() {
  return print_indexes( std::make_index_sequence<I>{} );
}

print_indexes_upto vuole creare e decomprimere un pacchetto di parametri degli indici. Per fare ciò, deve chiamare una funzione di supporto. Ogni volta che si desidera decomprimere un pacchetto di parametri creato, si finisce per dover creare una funzione di supporto personalizzata per farlo.

Questo può essere evitato con lambda.

È possibile decomprimere i pacchetti di parametri in un set di invocazioni di un lambda, come questo:

template<std::size_t I>
using index_t = std::integral_constant<std::size_t, I>;
template<std::size_t I>
constexpr index_t<I> index{};

template<class=void, std::size_t...Is>
auto index_over( std::index_sequence<Is...> ) {
  return [](auto&& f){
    using discard=int[];
    (void)discard{0,(void(
      f( index<Is> )
    ),0)...};
  };
}

template<std::size_t N>
auto index_over(index_t<N> = {}) {
  return index_over( std::make_index_sequence<N>{} );
}
C ++ 17

index_over() con espressioni fold può essere semplificato per:

template<class=void, std::size_t...Is>
auto index_over( std::index_sequence<Is...> ) {
  return [](auto&& f){
    ((void)(f(index<Is>)), ...);
  };
}

Dopo averlo fatto, puoi usare questo per sostituire dover decomprimere manualmente i pacchetti di parametri con un secondo sovraccarico in altro codice, permettendoti di decomprimere i pacchetti di parametri "in linea":

template<class Tup, class F>
void for_each_tuple_element(Tup&& tup, F&& f) {
  using T = std::remove_reference_t<Tup>;
  using std::tuple_size;
  auto from_zero_to_N = index_over< tuple_size<T>{} >();

  from_zero_to_N(
    [&](auto i){
      using std::get;
      f( get<i>( std::forward<Tup>(tup) ) );
    }
  );
}

L' auto i passato al lambda da index_over è uno std::integral_constant<std::size_t, ???> . Questo ha una conversione di constexpr in std::size_t che non dipende dallo stato di this , quindi possiamo usarlo come costante in fase di compilazione, come quando lo passiamo a std::get<i> sopra.

Per tornare all'esempio del giocattolo in alto, riscrivilo come:

template<std::size_t I>
void print_indexes_upto() {
  index_over(index<I>)([](auto i){
    std::cout << i << '\n'; // here i is a compile-time constant
  });
}

che è molto più breve e mantiene la logica nel codice che la usa.

Esempio dal vivo con cui giocare.



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow