Zoeken…


Syntaxis

  • [ default-capture , capture-lijst ] ( argument-lijst ) veranderbare attributen voor gooi-specificatie -> return-type { lambda-body } // Volgorde van lambda-specificatie en attributen.
  • [ capture-list ] ( argument-lijst ) { lambda-body } // Algemene lambda-definitie.
  • [=] ( argument-lijst ) { lambda-body } // Legt alle benodigde lokale variabelen op waarde vast.
  • [&] ( argument-lijst ) { lambda-body } // Legt alle benodigde lokale variabelen op referentie vast.
  • [ capture-list ] { lambda-body } // Argumentenlijst en specifiers kunnen worden weggelaten.

parameters

Parameter Details
default-capture Geeft aan hoe alle niet-vermelde variabelen worden vastgelegd. Kan = (vastleggen op waarde) of & (vastleggen op referentie) zijn. Indien weggelaten, zijn niet-vermelde variabelen ontoegankelijk binnen de lambda-body . De standaardopname moet voorafgaan aan de opnamelijst .
capture-lijst Geeft aan hoe lokale variabelen toegankelijk worden gemaakt binnen de lambda-body . Variabelen zonder voorvoegsel worden op waarde vastgelegd. Variabelen voorafgegaan door & worden vastgelegd door referentie. Binnen een klassemethode kan this worden gebruikt om al zijn leden via verwijzing toegankelijk te maken. Niet-vermelde variabelen zijn ontoegankelijk, tenzij de lijst wordt voorafgegaan door een standaardopname .
argument-lijst Specificeert de argumenten van de lambda-functie.
veranderlijk (optioneel) Normaal zijn variabelen vastgelegd door waarde const . Als u mutable mutable , worden ze niet-const. Wijzigingen in die variabelen blijven behouden tussen aanroepen.
throw-specificatie (optioneel) Hiermee geeft u het uitzonderingsworpgedrag van de lambda-functie op. Bijvoorbeeld: noexcept of throw(std::exception) .
attributen (optioneel) Alle kenmerken voor de lambdafunctie. Als het lambda-lichaam bijvoorbeeld altijd een uitzondering [[noreturn]] kan [[noreturn]] worden gebruikt.
-> retourtype (optioneel) Geeft het retourtype van de lambda-functie aan. Vereist wanneer het retourtype niet door de compiler kan worden bepaald.
lambda-body Een codeblok dat de implementatie van de lambda-functie bevat.

Opmerkingen

C ++ 17 (de huidige versie) introduceert constexpr , in feite lambdas die tijdens het compileren kunnen worden geëvalueerd. Een lambda is automatisch constexpr als deze voldoet constexpr vereisten voor constexpr , maar u kunt het ook opgeven met het trefwoord constexpr :

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

Wat is een lambda-uitdrukking?

Een lambda-expressie biedt een beknopte manier om eenvoudige functieobjecten te maken. Een lambda-expressie is een prvalue waarvan het resultaatobject afsluitingsobject wordt genoemd, dat zich gedraagt als een functieobject.

De naam 'lambda-expressie' is afkomstig van lambda-calculus , een wiskundig formalisme dat in de jaren 1930 door Alonzo Church is uitgevonden om vragen over logica en berekenbaarheid te onderzoeken. Lambda-calculus vormde de basis van LISP , een functionele programmeertaal. Vergeleken met lambda-calculus en LISP, delen C ++ lambda-expressies de eigenschappen dat ze niet genoemd worden en om variabelen uit de omringende context vast te leggen, maar ze missen de mogelijkheid om functies te bedienen en terug te geven.

Een lambda-expressie wordt vaak gebruikt als argument voor functies waarvoor een opvraagbaar object nodig is. Dat kan eenvoudiger zijn dan het creëren van een benoemde functie, die alleen zou worden gebruikt als deze als argument wordt doorgegeven. In dergelijke gevallen hebben lambda-expressies in het algemeen de voorkeur omdat ze het mogelijk maken om de functieobjecten inline te definiëren.

Een lambda bestaat meestal uit drie delen: een opnamelijst [] , een optionele parameterlijst () en een body {} , die allemaal leeg kunnen zijn:

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

Lijst met opnames

[] is de opnamelijst . Standaard zijn variabelen van het omsluitende bereik niet toegankelijk voor een lambda. Als u een variabele vastlegt, is deze toegankelijk in de lambda, als kopie of als referentie . Gevangen variabelen worden een onderdeel van de lambda; in tegenstelling tot functieargumenten hoeven ze niet te worden doorgegeven bij het aanroepen van de 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.

Parameter lijst

() is de parameterlijst , die bijna hetzelfde is als in normale functies. Als de lambda geen argumenten aanneemt, kunnen deze haakjes worden weggelaten (behalve als u de lambda mutable moet verklaren). Deze twee lambdas zijn gelijkwaardig:

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

De parameterlijst kan het type tijdelijke aanduiding auto plaats van de werkelijke typen. Hierdoor gedraagt dit argument zich als een sjabloonparameter van een functiesjabloon. De volgende lambdas zijn equivalent wanneer u een vector in generieke code wilt sorteren:

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

Functie lichaam

{} is het lichaam , wat hetzelfde is als bij normale functies.


Een lambda bellen

Het resultaatobject van een lambda-expressie is een afsluiting , die kan worden opgeroepen met de operator() (net als bij andere functieobjecten):

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

Retourtype

Standaard wordt het retourtype van een lambda-expressie afgeleid.

[](){ return true; };

In dit geval is het bool .

U kunt het retourtype ook handmatig opgeven met behulp van de volgende syntaxis:

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

Veranderlijke Lambda

Objecten die zijn vastgelegd met waarde in de lambda zijn standaard onveranderlijk. Dit komt omdat de operator() van het gegenereerde afsluitobject standaard const is.

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

Wijzigen kan worden toegestaan met behulp van het trefwoord mutable , waardoor de operator() het dichtstbijzijnde object niet- const :

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

Indien gebruikt in combinatie met het mutable komt mutable eraan vooraf.

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

Een voorbeeld om het nut van lambdas te illustreren

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

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

Het retourtype opgeven

Voor lambdas met een enkele retourverklaring of meerdere retourinstructies waarvan de uitdrukkingen van hetzelfde type zijn, kan de compiler het retourtype afleiden:

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

Voor lambdas met meerdere retouroverzichten van verschillende typen, kan de compiler het retourtype niet afleiden:

// 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 dit geval moet u het retourtype expliciet opgeven:

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

De regels hiervoor komen overeen met de regels voor auto typeaftrek. Lambdas zonder expliciet gespecificeerde retourtypen retourneren nooit referenties, dus als een referentietype gewenst is, moet dit ook expliciet worden gespecificeerd:

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

Vastleggen op waarde

Als u de naam van de variabele opgeeft in de opnamelijst, zal de lambda deze op waarde vastleggen. Dit betekent dat het gegenereerde sluitingstype voor de lambda een kopie van de variabele opslaat. Dit vereist ook dat het type variabele kan worden geconstrueerd :

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

Vanaf C ++ 14 is het mogelijk om ter plaatse variabelen te initialiseren. Hiermee kunnen alleen verplaatsingstypen worden vastgelegd in de lambda.

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

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

Hoewel een lambda variabelen op waarde vangt wanneer ze door hun naam worden gegeven, kunnen dergelijke variabelen niet standaard in de lambda-body worden gewijzigd. Dit komt omdat het type sluiting de lambda-body in een verklaring van operator() const plaatst.

De const toepassing op toegangen tot lidvariabelen van het sluitingstype en vastgelegde variabelen die lid zijn van de sluiting (alle tegenstellingen):

int a = 0;

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

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

Om de const te verwijderen, moet u het sleutelwoord mutable op de lambda opgeven:

int a = 0;

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

Omdat a werd vastgelegd door waarde, hebben wijzigingen die zijn aangebracht door de lambda aan te roepen geen invloed op a . De waarde van a werd gekopieerd in de lambda toen het werd gebouwd, zodat de lambda's kopie van a staat los van de externe a variabele.

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"

Gegeneraliseerde opname

C ++ 14

Lambdas kan uitdrukkingen vastleggen in plaats van alleen variabelen. Hiermee kunnen lambdas alleen-verplaatsingstypes opslaan:

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

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

Dit verplaatst de buitenste p variabele naar de lambda-vastlegvariabele, ook wel p . lamb bezit nu het geheugen toegewezen door make_unique . Omdat de sluiting een type bevat dat niet kan worden gekopieerd, betekent dit dat lamb zelf niet kan worden gekopieerd. Maar het kan worden verplaatst:

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

Nu bezit lamb_move de herinnering.


Merk op dat std::function<> vereist dat de opgeslagen waarden kopieerbaar zijn. Je kunt je eigen alleen-verplaatsen-vereiste std::function , of je kunt de lambda gewoon in een shared_ptr 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));

neemt onze alleen-verplaatsbare lambda en stopt zijn status in een gedeelde aanwijzer en retourneert vervolgens een lambda die kan worden gekopieerd en vervolgens kan worden opgeslagen in een std::function of iets dergelijks.


Gegeneraliseerde opname gebruikt auto typeaftrek voor het type van de variabele. Het zal deze vastleggingen standaard als waarden declareren, maar het kunnen ook referenties zijn:

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.

Generalize capture hoeft helemaal geen externe variabele vast te leggen. Het kan een willekeurige uitdrukking bevatten:

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

Dit is handig om lambdas willekeurige waarden te geven die ze kunnen behouden en mogelijk kunnen wijzigen, zonder ze extern aan de lambda te hoeven verklaren. Dat is natuurlijk alleen handig als je niet van plan bent om toegang te krijgen tot die variabelen nadat de lambda zijn werk heeft voltooid.

Vastleggen door referentie

Als u de naam van een lokale variabele voorafgaat door een & , wordt de variabele vastgelegd als referentie. Conceptueel betekent dit dat het type sluiting van de lambda een referentievariabele zal hebben, geïnitialiseerd als een verwijzing naar de overeenkomstige variabele van buiten het bereik van de lambda. Elk gebruik van de variabele in de lambda-body verwijst naar de oorspronkelijke variabele:

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

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

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

Het sleutelwoord mutable is niet nodig, omdat a zelf niet const .

Natuurlijk betekent vastleggen door verwijzing dat de lambda niet mag ontsnappen aan het bereik van de variabelen die het vastlegt. Je kunt dus functies aanroepen die een functie aannemen, maar je mag geen functie aanroepen die de lambda buiten het bereik van je referenties opslaat . En je moet de lambda niet teruggeven.

Standaard opname

Standaard zijn lokale variabelen die niet expliciet in de opnamelijst zijn opgegeven, niet toegankelijk vanuit de lambda-body. Het is echter mogelijk om impliciet variabelen vast te leggen die door de lambda-body worden genoemd:

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

Expliciet vastleggen kan nog steeds naast impliciet vastleggen. De expliciete vastlegdefinitie vervangt de standaard vastlegging:

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

Generieke lambdas

C ++ 14

Lambda-functies kunnen argumenten van willekeurige typen aannemen. Hierdoor kan een lambda meer generiek zijn:

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

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

Dit wordt geïmplementeerd in C ++ door de operator() het type sluiting te overbelasten tot een sjabloonfunctie. Het volgende type heeft hetzelfde gedrag als de bovenstaande lambdasluiting:

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

Niet alle parameters in een generieke lambda hoeven generiek te zijn:

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

Hier wordt x afgeleid op basis van het eerste functieargument, terwijl y altijd int zal zijn.

Generieke lambdas kunnen argumenten ook door middel van verwijzing aannemen, met behulp van de gebruikelijke regels voor auto en & . Als een generieke parameter als auto&& , is dit een doorstuurreferentie naar het doorgegeven argument en geen waardeverwijzing :

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

Lambda-functies kunnen variadisch zijn en hun argumenten perfect doorgeven:

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

of:

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

die alleen "correct" werkt met variabelen van het type auto&& .

Een goede reden om generieke lambdas te gebruiken, is om de syntaxis te bezoeken.

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

Hier bezoeken we op een polymorfe manier; maar in andere contexten zijn de namen van het type dat we passeren niet interessant:

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

Herhaling van het type std::ostream& is noise hier; het zou zijn alsof je het type variabele telkens moet vermelden wanneer je deze gebruikt. Hier creëren we een bezoeker, maar geen polymorfe; auto wordt om dezelfde reden gebruikt als je auto zou kunnen gebruiken in een for(:) -lus.

Conversie naar functie pointer

Als de opnamelijst van een lambda leeg is, heeft de lambda een impliciete conversie naar een functiepointer die dezelfde argumenten aanneemt en hetzelfde retourtype retourneert:

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

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

Een dergelijke conversie kan ook worden afgedwongen met behulp van unary plus operator:

func_ptr sorter_func2 = +sorter; // enforce implicit conversion

Het aanroepen van deze functie-aanwijzer gedraagt zich precies als het aanroepen van operator() op de lambda. Deze functie-aanwijzer is op geen enkele manier afhankelijk van het bestaan van de bron lambda-sluiting. Het kan daarom de lambdasluiting overleven.

Deze functie is vooral handig voor het gebruik van lambda's met API's die functiewijzers behandelen, in plaats van C ++ functieobjecten.

C ++ 14

Conversie naar een functiepointer is ook mogelijk voor generieke lambdas met een lege opnamelijst. Indien nodig wordt sjabloonargumentaftrek gebruikt om de juiste specialisatie te selecteren.

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;

Klasse lambdas en vangst hiervan

Een lambda-expressie die wordt geëvalueerd in de lidfunctie van een klasse is impliciet een vriend van die klasse:

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

Zo'n lambda is niet alleen een vriend van die klasse, hij heeft dezelfde toegang als de klasse waarin hij wordt verklaard.

Lambdas kan this aanwijzer vastleggen die de objectinstantie voorstelt waarvoor de externe functie is gebruikt. Dit wordt gedaan door this te voegen aan de opnamelijst:

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

Wanneer this wordt vastgelegd, kan de lambda lidnamen van zijn bevattende klasse gebruiken alsof het in zijn bevattende klasse was. Dus een impliciete this-> wordt op dergelijke leden toegepast.

Let op: this wordt vastgelegd door waarde, maar niet door de waarde van het type. Het wordt gevangen door de waarde this , wat een wijzer is . Als zodanig heeft de lambda geen eigenaar van this . Als de lambda de levensduur van het object dat het heeft gemaakt leeft, kan de lambda ongeldig worden.

Dit betekent ook dat de lambda kunt wijzigen this zonder aangifte mutable . Het is de wijzer die const , niet het object waarnaar wordt const . Dat wil zeggen, tenzij de buitenste const zelf een const functie was.

Ook beseffen dat de standaard vast te leggen bepalingen, zowel [=] en [&] , zal ook vast te leggen this impliciet. En ze vangen het allebei door de waarde van de aanwijzer. Het is inderdaad een fout om this in de opnamelijst op te geven wanneer een standaard wordt gegeven.

C ++ 17

Lambdas kan een kopie maken van this object, gemaakt op het moment dat de lambda is gemaakt. Dit wordt gedaan door *this te voegen aan de opnamelijst:

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

Lambdafuncties porteren naar C ++ 03 met behulp van functoren

Lambda functies in C ++ zijn syntactische suiker dat een beknopte syntax voor het schrijven functors . Als zodanig kan equivalente functionaliteit worden verkregen in C ++ 03 (zij het veel uitgebreider) door de lambdafunctie om te zetten in een 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;
}

Als de lambdafunctie mutable is, maak dan de call-operator van de functor niet-const, dwz:

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

Recursieve lambdas

Laten we zeggen dat we Euclid's gcd() willen schrijven als een lambda. Als functie is het:

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

Maar een lambda kan niet recursief zijn, hij kan zichzelf niet inroepen. Een lambda heeft geen naam en het gebruik this in het lichaam van een lambda verwijst naar een gevangen this (ervan uitgaande dat de lambda is gecreëerd in het lichaam van een lidfunctie, anders is het een fout). Dus hoe lossen we dit probleem op?

Gebruik de std::function

We kunnen een lambda een verwijzing laten vastleggen naar een nog niet geconstrueerde std::function :

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

Dit werkt, maar moet spaarzaam worden gebruikt. Het is traag (we gebruiken nu type wissen in plaats van een directe functie-aanroep), het is kwetsbaar (het kopiëren van gcd of het teruggeven van gcd zal breken omdat de lambda naar het oorspronkelijke object verwijst), en het zal niet werken met generieke lambdas.

Twee slimme aanwijzers gebruiken:

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

Dit voegt veel richting toe (wat overhead is), maar het kan worden gekopieerd / geretourneerd en alle exemplaren delen de status. Het laat je de lambda teruggeven en is anders minder kwetsbaar dan de bovenstaande oplossing.

Gebruik een Y-combinator

Met behulp van een korte hulpprogramma-structuur kunnen we al deze problemen oplossen:

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)

we kunnen onze gcd implementeren als:

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

De y_combinator is een concept uit de lambda-calculus waarmee u recursie kunt krijgen zonder uzelf een naam te kunnen geven totdat u bent gedefinieerd. Dit is precies het probleem dat lambdas hebben.

Je creëert een lambda die "recurse" als eerste argument neemt. Als je wilt recidiveren, geef je de argumenten door aan recidive.

De y_combinator retourneert vervolgens een functieobject dat die functie aanroept met zijn argumenten, maar met een geschikt "recurse" -object (namelijk de y_combinator zelf) als zijn eerste argument. Het stuurt de rest van de argumenten waarmee je de y_combinator door naar de lambda.

In het kort:

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

en je hebt recursie in een lambda zonder serieuze beperkingen of aanzienlijke overhead.

Lambdas gebruiken voor inline uitpakken van parameterpakketten

C ++ 14

Parameterpakket uitpakken vereist traditioneel het schrijven van een helperfunctie voor elke keer dat u het wilt doen.

In dit speelgoedvoorbeeld:

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

De print_indexes_upto wil een parameterpakket met indexen maken en uitpakken. Om dit te doen, moet het een helperfunctie aanroepen. Telkens wanneer u een door u gemaakt parameterpakket wilt uitpakken, moet u daarvoor een aangepaste helperfunctie maken.

Dit kan worden vermeden met lambdas.

Je kunt parameterpakketten uitpakken in een reeks aanroepen van een lambda, zoals hier:

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

Met index_over() kan index_over() worden vereenvoudigd om:

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

Zodra u dat hebt gedaan, kunt u dit gebruiken om het handmatig uitpakken van parameterpakketten te vervangen door een tweede overbelasting in andere code, zodat u parameterpakketten "inline" kunt uitpakken:

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

De auto i aan de lambda auto i doorgegeven door de index_over is een std::integral_constant<std::size_t, ???> . Dit heeft een constexpr conversie naar std::size_t die niet afhankelijk is van de status this , dus we kunnen het gebruiken als een compilatie-tijdconstante, zoals wanneer we het doorgeven aan std::get<i> hierboven.

Om terug te gaan naar het speelgoedvoorbeeld bovenaan, herschrijf het als volgt:

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

die veel korter is en logica bevat in de code die deze gebruikt.

Live voorbeeld om mee te spelen.



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow