Sök…


Syntax

  • [ default-capture , capture-list ] ( argument-list ) mutable throw-specificification attribut -> return-type { lambda-body } // Order of lambda specificiers and attribut.
  • [ capture-list ] ( argument-list ) { lambda-body } // Common lambda definition.
  • [=] ( argumentlista ) { lambda-body } // Fångar alla nödvändiga lokala variabler efter värde.
  • [&] ( argumentlista ) { lambda-body } // Fångar alla nödvändiga lokala variabler genom referens.
  • [ capture-list ] { lambda-body } // Argumentlista och specifikatörer kan utelämnas.

parametrar

Parameter detaljer
default-capture Anger hur alla icke-listade variabler fångas. Kan vara = (fånga efter värde) eller & (fånga med referens). Om utelämnade är icke-listade variabler otillgängliga inom lambda-kroppen . Standardfångsten måste föregå fångstlistan .
capture-lista Anger hur lokala variabler görs tillgängliga i lambda-kroppen . Variabler utan prefix fångas med värde. Variabler förinställda med & fångas med referens. Inom en klassmetod kan this användas för att göra alla medlemmar tillgängliga genom referens. Icke-listade variabler är otillgängliga, om inte listan föregås av en standardfångst .
argument-lista Anger argumenten för lambda-funktionen.
föränderlig (valfritt) Normalt är variabler som fångats med värde const . Att specificera mutable gör dem icke-const. Ändringar av dessa variabler behålls mellan samtal.
throw-specifikationen (valfritt) Anger undantagskastbeteendet för lambda-funktionen. Till exempel: noexcept eller throw(std::exception) .
attribut (valfritt) Alla attribut för lambda-funktionen. Till exempel, om lambda-kroppen alltid kastar ett undantag, kan [[noreturn]] användas.
-> returtyp (valfritt) Anger returtyp för lambda-funktionen. Krävs när returtypen inte kan bestämmas av kompilatorn.
lambda-kropp Ett kodblock som innehåller implementeringen av lambda-funktionen.

Anmärkningar

C ++ 17 (det nuvarande utkastet) introducerar constexpr lambdas, i princip lammadas som kan utvärderas vid sammanställningstiden. En lambda är automatiskt constexpr om den uppfyller constexpr kraven, men du kan också ange den med hjälp av constexpr nyckelordet:

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

Vad är ett lambda-uttryck?

Ett lambda-uttryck ger ett kortfattat sätt att skapa enkla funktionsobjekt. Ett lambda-uttryck är en uppskattning vars resultatobjekt kallas stängningsobjekt , som uppträder som ett funktionsobjekt.

Namnet "lambda-uttryck" härstammar från lambda-beräkningen , som är en matematisk formalism som uppfanns på 1930-talet av Alonzo-kyrkan för att undersöka frågor om logik och beräkningsbarhet. Lambdakalkylen baserade sig på LISP , ett funktionellt programmeringsspråk. Jämfört med lambda-beräkningen och LISP delar C ++ lambda-uttryck egenskaperna för att vara obetecknade och att fånga variabler från det omgivande sammanhanget, men de saknar förmågan att driva och återföra funktioner.

Ett lambda-uttryck används ofta som ett argument till funktioner som tar ett kallbart objekt. Det kan vara enklare än att skapa en namngiven funktion, som bara skulle användas när den skickas som argument. I sådana fall är lambda-uttryck generellt föredragna eftersom de tillåter att definiera funktionsobjekten inline.

En lambda består vanligtvis av tre delar: en fångstlista [] , en valfri parameterlista () och en kropp {} , som alla kan vara tomma:

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

Fånga lista

[] är inspelningslistan . Som standard kan inte variabler i det bifogade omfånget nås av en lambda. Att fånga en variabel gör den tillgänglig i lambda, antingen som en kopia eller som en referens . Fångade variabler blir en del av lambda; i motsats till funktionsargument behöver de inte skickas vid samtal till 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.

Parameterlista

() är parameterlistan , som är nästan densamma som i vanliga funktioner. Om lambda inte tar några argument kan dessa parenteser utelämnas (förutom om du behöver förklara lambdaen som kan vara mutable ). Dessa två lambdor är likvärdiga:

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

Parameterlistan kan använda platshållartypen auto istället för faktiska typer. Genom att göra det, uppträder detta argument som en mallparameter för en funktionsmall. Följande lambdas är likvärdiga när du vill sortera en vektor i generisk kod:

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

Funktion kropp

{} är kroppen , som är densamma som i vanliga funktioner.


Ringer en lambda

Ett lambda-uttrycks resultatobjekt är en stängning , som kan kallas med operator() (som med andra funktionsobjekt):

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

Returtyp

Som standard dras returtypen för ett lambda-uttryck ut.

[](){ return true; };

I detta fall är bool .

Du kan också ange returtyp manuellt med följande syntax:

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

Muterbar Lambda

Objekt som fångats med värde i lambda är som standard obegränsade. Detta beror på att operator() för det genererade stängningsobjektet är som standard const .

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

Modifiering kan tillåtas med hjälp av nyckelordet som kan mutable , vilket gör det närmaste objektets operator() icke- const

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

Om den används tillsammans med mutable kommer mutable före den.

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

Ett exempel för att illustrera nyttan av lambdas

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

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

Ange returtyp

För lammadas med ett enda retursuttalande eller flera returrättningar vars uttryck är av samma typ kan kompilatorn härleda returtypen:

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

För lambdas med flera returrättningar av olika typer kan kompilatorn inte härleda returtypen:

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

I detta fall måste du ange returtypen uttryckligen:

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

Reglerna för denna match reglerna för auto typ avdrag. Lambdas utan uttryckligen specificerade returtyper returnerar aldrig referenser, så om en referenstyp önskas måste den också anges uttryckligen:

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

Fånga efter värde

Om du specificerar variabelns namn i fångstlistan kommer lambda att fånga den efter värde. Detta innebär att den genererade stängningstypen för lambda lagrar en kopia av variabeln. Detta kräver också att variabelns typ är kopierbar :

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

Från C ++ 14 är det möjligt att initiera variabler på plats. Detta gör att endast drag kan fångas i lambda.

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

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

Även om en lambda fångar in variabler efter värde när de ges av deras namn, kan sådana variabler inte ändras inom lambda-kroppen som standard. Detta beror på att stängningstypen sätter lambda-kroppen i en deklaration av operator() const .

const gäller för åtkomst till medlemsvariabler av stängningstyp och fångade variabler som är medlemmar i stängningen (allt motsatt utseende):

int a = 0;

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

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

För att ta bort const måste du ange nyckelordet som kan mutable på lambda:

int a = 0;

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

Eftersom a fångades med värde, påverkar inte alla ändringar som görs genom att ringa lambda a . Värdet på a kopierades till lambda när det konstruerades, så lambdas kopia av a är separat från den externa a variabel.

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"

Generaliserad fångst

C ++ 14

Lambdas kan fånga uttryck snarare än bara variabler. Detta tillåter lambdas att lagra flyttbara typer:

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

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

Detta flyttar den yttre p variabeln till lambda-fångstvariabeln, även kallad p . lamb äger nu det minne som tilldelas av make_unique . Eftersom förslutningen innehåller en typ som inte kan kopieras, betyder detta att lamb är icke-kopierbart. Men det kan flyttas:

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

Nu äger lamb_move minnet.


Observera att std::function<> kräver att de lagrade värdena kan kopieras. Du kan skriva din egen std::function bara behöver flytta , eller så kan du bara fylla lambda i en shared_ptr omslag:

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

tar vår flyttbara lambda och fyller dess tillstånd i en delad pekare och returnerar sedan en lambda som kan kopieras och sedan lagras i en std::function eller liknande.


Generaliserad capture använder auto typ avdrag för variabeln typ. Det kommer att förklara dessa tagningar som värden som standard, men de kan också vara referenser:

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.

Generalisera fångst behöver inte fånga en extern variabel alls. Det kan fånga ett godtyckligt uttryck:

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

Detta är användbart för att ge lambdas godtyckliga värden som de kan hålla och potentiellt modifiera, utan att behöva förklara dem externt till lambda. Naturligtvis är det bara användbart om du inte tänker komma åt dessa variabler efter att lambda har slutfört sitt arbete.

Fånga genom referens

Om du föregår en lokal variabelns namn med ett & , kommer fångstvariablen att fångas med referens. Konceptuellt betyder detta att lambdas stängningstyp kommer att ha en referensvariabel, initialiserad som referens till motsvarande variabel utanför lambdas omfattning. All användning av variabeln i lambda-kroppen refererar till den ursprungliga variabeln:

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

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

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

Nyckelordet kan mutable inte, eftersom a sig inte är const .

Naturligtvis innebär att fånga genom referens att lambda inte får undgå omfattningen av de variabler som den fångar. Så du kan ringa funktioner som tar en funktion, men du får inte ringa en funktion som lagrar lambda utanför ramen för dina referenser. Och du får inte returnera lambdaen.

Standardupptagning

Som standard kan inte lokala variabler som inte uttryckligen anges i fångstlistan nås från lambda-kroppen. Det är emellertid möjligt att implicit fånga variabler som namnges av lambda-kroppen:

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

Explicit capturing kan fortfarande göras tillsammans med implicit standardinspelning. Den explicita fångstdefinitionen kommer att åsidosätta standardfångsten:

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

Generiska lambdas

c ++ 14

Lambdafunktioner kan ta argument av godtyckliga typer. Detta gör att en lambda kan vara mer generisk:

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

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

Detta implementeras i C ++ genom att göra operator() överbelastningstyp till en mallfunktion. Följande typ har motsvarande beteende med ovanstående lambda-stängning:

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

Inte alla parametrar i en generisk lambda behöver vara generiska:

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

Här x baserat på det första funktionsargumentet, medan y alltid kommer att vara int .

Generiska lambdas kan också ta argument som referens genom att använda de vanliga reglerna för auto och & . Om en generisk parameter tas som auto&& , är detta en vidarebefordringsreferens till det skickade i argumentet och inte en värderingsreferens :

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

Lambdafunktioner kan vara varierande och framför sina argument perfekt:

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

eller:

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

som bara fungerar "ordentligt" med variabler av typen auto&& .

En stark anledning att använda generiska lambdas är för att besöka syntax.

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

Här besöker vi på ett polymorft sätt; men i andra sammanhang är namnen på den typ vi passerar inte intressanta:

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

Upprepa typen av std::ostream& är buller här; det skulle vara som att behöva nämna typen av en variabel varje gång du använder den. Här skapar vi en besökare, men ingen polymorf; auto används av samma anledning som du kan använda auto i en for(:) slinga.

Konvertering till funktionspekare

Om en lambdas fånglista är tom, har lambda en implicit konvertering till en funktionspekare som tar samma argument och returnerar samma returtyp:

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

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

En sådan omvandling kan också verkställas med hjälp av unary plus-operatör:

func_ptr sorter_func2 = +sorter; // enforce implicit conversion

Att kalla denna funktion pekaren fungerar exakt som att påkalla operator() på lambda. Denna funktionspekare är inte på något sätt beroende av källan lambda stängning existens. Det kan därför överleva lambda-stängningen.

Den här funktionen är främst användbar för att använda lambdas med API: er som hanterar funktionspekare, snarare än C ++ funktionsobjekt.

C ++ 14

Konvertering till en funktionspekare är också möjlig för generiska lambdas med en tom fångslista. Om det behövs kommer mallargumentavdrag att användas för att välja rätt specialisering.

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;

Klassa lambdas och fånga detta

Ett lambda-uttryck utvärderat i en klassmedlemfunktion är implicit en vän till den klassen:

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

En sådan lambda är inte bara en vän till den klassen, den har samma tillgång som den klass den förklaras inom har.

Lambdas kan fånga den this pekaren som representerar objektinstansen den yttre funktionen kallades på. Detta görs genom att lägga till this i fångstlistan:

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

När this fångas kan lambda använda medlemsnamn på den innehållande klassen som om den var i dess innehållande klass. Så en implicit this-> tillämpas på sådana medlemmar.

Var medveten om att this fångas av värde, men inte värdet på typen. Det fångas av värdet på this , som är en pekare . Som sådan, inte lambda inte äger this . Om lambda ut lever livet för objektet som skapade det, kan lambda bli ogiltig.

Detta innebär också att lambda kan modifiera this utan att förklaras mutable . Det är pekaren som är const , inte föremålet som pekas på. Det vill säga, såvida inte den yttre medlemsfunktionen i sig var en const .

Också vara medveten om att standard fånga klausuler både [=] och [&] , kommer också att fånga upp this underförstått. Och de båda fångar den efter värdet på pekaren. Det är faktiskt ett fel att ange this i fångstlistan när en standard ges.

C ++ 17

Lambdas kan fånga en kopia av this objekt, skapat vid den tidpunkt då lambda skapades. Detta görs genom att lägga till *this i fångstlistan:

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

Porting lambda-funktioner till C ++ 03 med funktorer

Lambdafunktioner i C ++ är syntaktiskt socker som ger en mycket kort syntax för att skriva funktorer . Som sådan kan ekvivalent funktionalitet erhållas i C ++ 03 (om än mycket mer ordligt) genom att konvertera lambda-funktionen till en funktor:

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

Om lambda-funktionen är mutable gör funktorns samtaloperatör icke-const, dvs.

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

Rekursiva lambdas

Låt oss säga att vi vill skriva Euclids gcd() som en lambda. Som en funktion är det:

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

Men en lambda kan inte vara rekursiv, den har inget sätt att åberopa sig själv. En lambda har inget namn och att använda this i en lambdas kropp refererar till en fångad this (förutsatt att lambda är skapad i kroppen för en medlemsfunktion, annars är det ett fel). Så hur löser vi detta problem?

Använd std::function

Vi kan låta en lambda fånga en hänvisning till en ännu inte konstruerad std::function :

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

Detta fungerar, men bör användas sparsamt. Det är långsamt (vi använder typ-radering nu istället för ett direktfunktionssamtal), det är bräckligt (att kopiera gcd eller returnera gcd kommer att gå sönder eftersom lambda hänvisar till det ursprungliga objektet), och det fungerar inte med generiska lambdas.

Använda två smarta pekare:

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

Detta lägger till mycket indirekt (vilket är överhead), men det kan kopieras / returneras, och alla kopior delar tillstånd. Det låter dig returnera lambda och är annars mindre ömtålig än ovanstående lösning.

Använd en Y-kombinator

Med hjälp av en kort verktygsstruktur kan vi lösa alla dessa problem:

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)

vi kan implementera vår gcd som:

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

y_combinator är ett koncept från lambda-kalkylen som låter dig få rekursion utan att kunna namnge dig själv tills du är definierad. Detta är exakt problemet som lambdas har.

Du skapar en lambda som tar "recurse" som sitt första argument. När du vill återvände överför du argumenten för att återanvänder.

y_combinator returnerar sedan ett funktionsobjekt som kallar den funktionen med sina argument, men med ett lämpligt "recurse" -objekt (nämligen y_combinator själv) som sitt första argument. Den vidarebefordrar också resten av argumenten du kallar y_combinator till lambda.

Kortfattat:

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

och du har rekursion i en lambda utan några allvarliga begränsningar eller betydande omkostnader.

Använda lambdas för inpackning av inlineparametern

C ++ 14

Parameterpaket som packar upp traditionellt kräver att du skriver en hjälpfunktion för varje gång du vill göra det.

I detta leksaksexempel:

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 vill skapa och packa upp ett parameterpaket med index. För att göra det måste den ringa en hjälpfunktion. Varje gång du vill packa upp ett parameterpaket som du skapade måste du skapa en anpassad hjälpfunktion för att göra det.

Detta kan undvikas med lambdas.

Du kan packa upp parameterpaket i en uppsättning invokationer av en lambda, så här:

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

Med index_over() kan index_over() förenklas till:

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

När du har gjort det kan du använda detta för att ersätta att manuellt måste packa upp parameterpaket med en andra överbelastning i annan kod, så att du kan packa upp parameterpaket "inline":

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

auto i skickade till lambda av index_over är en std::integral_constant<std::size_t, ???> . Detta har en constexpr konvertering till std::size_t som inte beror på tillståndet för this , så vi kan använda den som en kompileringstidskonstant, till exempel när vi skickar den till std::get<i> ovan.

Om du vill gå tillbaka till leksakseksemplet längst upp skriver du det som:

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

vilket är mycket kortare och håller logiken i koden som använder den.

Levande exempel att spela med.



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow