Co to jest wyrażenie lambda w C ++ 11?


1485

Co to jest wyrażenie lambda w C ++ 11? Kiedy miałbym go użyć? Jakiego rodzaju problemy rozwiązują, które nie były możliwe przed ich wprowadzeniem?

Przydałoby się kilka przykładów i przypadków użycia.


14
Widziałem przypadek, w którym lambda była bardzo przydatna: mój kolega tworzył kod, który ma miliony iteracji, aby rozwiązać problem optymalizacji przestrzeni. Algorytm był znacznie szybszy przy użyciu lambda niż właściwej funkcji! Kompilatorem jest Visual C ++ 2013.
sergiol

Odpowiedzi:


1489

Problem

C ++ zawiera przydatne funkcje ogólne, takie jak std::for_eachi std::transform, które mogą być bardzo przydatne. Niestety mogą być również uciążliwe w użyciu, szczególnie jeśli funktor , który chcesz zastosować, jest unikalny dla konkretnej funkcji.

#include <algorithm>
#include <vector>

namespace {
  struct f {
    void operator()(int) {
      // do something
    }
  };
}

void func(std::vector<int>& v) {
  f f;
  std::for_each(v.begin(), v.end(), f);
}

Jeśli użyjesz tylko fraz i w tym konkretnym miejscu, wydaje się przesadą pisać całą klasę, aby zrobić coś trywialnego i jednorazowego.

W C ++ 03 możesz mieć ochotę napisać coś takiego, aby utrzymać funktor lokalny:

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

jednak nie jest to dozwolone, fnie można go przekazać do funkcji szablonu w C ++ 03.

Nowe rozwiązanie

C ++ 11 wprowadza lambdas, które pozwalają napisać wbudowany, anonimowy funktor, który zastąpi struct f. W przypadku małych prostych przykładów może to być łatwiejsze do odczytania (utrzymuje wszystko w jednym miejscu) i potencjalnie prostsze w utrzymaniu, na przykład w najprostszej formie:

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

Funkcje lambda to po prostu cukier syntaktyczny dla anonimowych funktorów.

Typy zwrotów

W prostych przypadkach obliczany jest typ zwrotu lambda, np .:

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

jednak kiedy zaczniesz pisać bardziej złożone lambdy, szybko napotkasz przypadki, w których kompilator nie może wydedukować typu zwrotu, np .:

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

Aby rozwiązać ten problem, możesz jawnie określić typ zwracany dla funkcji lambda, używając -> T:

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

Zmienne „przechwytywanie”

Do tej pory nie używaliśmy niczego innego niż to, co zostało przekazane do niej w lambdzie, ale możemy również używać innych zmiennych w obrębie lambda. Jeśli chcesz uzyskać dostęp do innych zmiennych, możesz użyć klauzuli przechwytywania ( []wyrażenia), która do tej pory nie była używana w tych przykładach, np .:

void func5(std::vector<double>& v, const double& epsilon) {
    std::transform(v.begin(), v.end(), v.begin(),
        [epsilon](double d) -> double {
            if (d < epsilon) {
                return 0;
            } else {
                return d;
            }
        });
}

Możesz przechwytywać zarówno według odwołania, jak i wartości, które możesz określić za pomocą &i =odpowiednio:

  • [&epsilon] przechwytywanie przez odniesienie
  • [&] przechwytuje wszystkie zmienne używane w lambda przez odniesienie
  • [=] przechwytuje wszystkie zmienne używane w lambda według wartości
  • [&, epsilon] przechwytuje zmienne takie jak z [&], ale epsilon według wartości
  • [=, &epsilon] przechwytuje zmienne jak z [=], ale epsilon przez odniesienie

Generowane operator()jest constdomyślnie, z implikacją, że przechwytywanie nastąpi constpo domyślnym dostępie do nich. Powoduje to, że każde wywołanie z tym samym wejściem daje ten sam wynik, jednak można oznaczyć lambda jakomutable żądanie, operator()aby nie zostało wygenerowane const.


9
@Yakk zostałeś uwięziony. lambd bez przechwytywania mają niejawną konwersję do wskaźników typu funkcji. funkcja konwersji jest constzawsze ...
Johannes Schaub - litb

2
@ JohannesSchaub-litb oh podstępny - i zdarza się, gdy wywołujesz ()- jest przekazywany jako lambda z zerowym argumentem, ale ponieważ () constnie pasuje do lambda, szuka konwersji typu, która na to pozwala, w tym niejawnego rzutowania -do-funkcji-wskaźnik, a następnie wywołuje to! Podstępny!
Yakk - Adam Nevraumont

2
Ciekawe - początkowo myślałem, że lambdas są anonimowymi funkcjami, a nie funktorami, i byłem zdezorientowany, jak działają przechwytywania.
user253751

49
Jeśli chcesz używać lambdas jako zmiennych w swoim programie, możesz użyć: std::function<double(int, bool)> f = [](int a, bool b) -> double { ... }; Ale zwykle pozwalamy kompilatorowi wydedukować typ: auto f = [](int a, bool b) -> double { ... }; (i nie zapomnij #include <functional>)
Evert Heylen

11
Przypuszczam, że nie wszyscy rozumieją, dlaczego return d < 0.00001 ? 0 : d;gwarantowany jest zwrot podwójny, gdy jeden z operandów jest stałą całkowitą (wynika to z domyślnej reguły promocji operatora?: Operator, w której drugi i trzeci operand są równoważone względem siebie za pomocą zwykłej arytmetyki konwersje bez względu na to, który zostanie wybrany). Zmiana na 0.0 : dbyć może ułatwiłaby zrozumienie przykładu.
Lundin,

828

Co to jest funkcja lambda?

Koncepcja C ++ funkcji lambda wywodzi się z rachunku lambda i programowania funkcjonalnego. Lambda jest nienazwaną funkcją przydatną (w rzeczywistym programowaniu, nie w teorii) dla krótkich fragmentów kodu, których nie można ponownie użyć i których nie warto nazywać.

W C ++ funkcja lambda jest zdefiniowana w ten sposób

[]() { } // barebone lambda

lub w całej okazałości

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

[]to lista przechwytywania, lista ()argumentów i {}treść funkcji.

Lista przechwytywania

Lista przechwytywania określa, co z zewnątrz lambda powinno być dostępne wewnątrz ciała funkcji i jak. Może to być:

  1. wartość: [x]
  2. referencja [i x]
  3. dowolna zmienna obecnie objęta zakresem przez odniesienie [&]
  4. taki sam jak 3, ale według wartości [=]

Możesz mieszać dowolne z powyższych elementów na liście oddzielonej przecinkami [x, &y].

Lista argumentów

Lista argumentów jest taka sama jak w każdej innej funkcji C ++.

Ciało funkcji

Kod, który zostanie wykonany po wywołaniu lambda.

Odliczenie typu zwrotu

Jeśli lambda ma tylko jedną instrukcję return, typ zwracany można pominąć i ma domyślny typ decltype(return_statement).

Zmienny

Jeśli lambda jest oznaczona jako zmienna (np. []() mutable { }), Można mutować wartości, które zostały przechwycone przez wartość.

Przypadków użycia

Biblioteka zdefiniowana w standardzie ISO znacznie korzysta z lambdas i podnosi użyteczność kilku pasków, ponieważ teraz użytkownicy nie muszą zaśmiecać kodu małymi funktorami w dostępnym zakresie.

C ++ 14

W C ++ 14 lambdów zostało poszerzonych o różne propozycje.

Zainicjowano przechwyty lambda

Teraz można zainicjować element listy przechwytywania =. Pozwala to na zmianę nazw zmiennych i przechwytywanie przez przenoszenie. Przykład wzięty ze standardu:

int x = 4;
auto y = [&r = x, x = x+1]()->int {
            r += 2;
            return x+2;
         }();  // Updates ::x to 6, and initializes y to 7.

i jeden wzięty z Wikipedii pokazujący, jak robić zdjęcia std::move:

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

Ogólne Lambdas

Lambdas mogą być teraz ogólne ( autobyłoby to równoważne z Ttym, gdyby Tgdzieś w otaczającym zakresie był argument szablonu typu):

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

Ulepszone odliczenie typu zwrotu

C ++ 14 pozwala przewidywać typy zwrotów dla każdej funkcji i nie ogranicza jej do funkcji formularza return expression;. Dotyczy to również lambd.


2
W powyższym przykładzie zainicjowanych zrzutów lambda, dlaczego kończysz funkcję Lamba za pomocą () ;? Wygląda to jak [] () {} (); zamiast [](){};. Czy wartość x również nie powinna wynosić 5?
Ramakrishnan Kannan

6
@RamakrishnanKannan: 1) () są tam, aby wywołać lambda zaraz po zdefiniowaniu i podać jej wartość zwracaną. Zmienna y jest liczbą całkowitą, a nie lambda. 2) Nie, x = 5 jest lokalne dla lambda (przechwytywanie według wartości, które akurat ma taką samą nazwę jak zmienna zakresu zewnętrznego x), a następnie zwracane jest x + 2 = 5 + 2. Ponowne przypisanie zmiennej zewnętrznej x odbywa się poprzez odniesienie r:, r = &x; r += 2;ale dzieje się tak z pierwotną wartością 4.
The Vee

167

Wyrażenia lambda są zwykle używane do enkapsulacji algorytmów, aby można je było przekazać do innej funkcji. Jednak możliwe jest natychmiastowe wykonanie lambda od definicji :

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

jest funkcjonalnie równoważny z

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

To sprawia, że ​​wyrażenia lambda są potężnym narzędziem do refaktoryzacji złożonych funkcji . Zaczynasz od zawijania sekcji kodu w funkcji lambda, jak pokazano powyżej. Proces jawnej parametryzacji można następnie przeprowadzać stopniowo za pomocą testów pośrednich po każdym kroku. Po pełnym sparametryzowaniu bloku kodu (jak pokazano po usunięciu &), możesz przenieść kod do zewnętrznego miejsca i ustawić go jako normalną funkcję.

Podobnie możesz użyć wyrażeń lambda do zainicjowania zmiennych na podstawie wyniku algorytmu ...

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

Jako sposób na podzielenie logiki programu może okazać się przydatne przekazanie wyrażenia lambda jako argumentu do innego wyrażenia lambda ...

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

Wyrażenia lambda pozwalają również tworzyć nazwane funkcje zagnieżdżone , co może być wygodnym sposobem uniknięcia podwójnej logiki. Używanie nazwanych lambdas jest również nieco łatwiejsze dla oczu (w porównaniu do anonimowych lambdów wbudowanych), gdy przekazuje się nietrywialną funkcję jako parametr do innej funkcji. Uwaga: nie zapomnij o średniku po zamykającym nawiasie klamrowym.

auto algorithm = [&]( double x, double m, double b ) -> double
   {
   return m*x+b;
   };

int a=algorithm(1,2,3), b=algorithm(4,5,6);

Jeśli kolejne profilowanie ujawni znaczny narzut związany z inicjalizacją dla obiektu funkcji, możesz przepisać to jako normalną funkcję.


11
Czy zdałeś sobie sprawę, że to pytanie zostało zadane 1,5 roku temu, a ostatnia aktywność miała miejsce prawie rok temu? W każdym razie wnosisz ciekawe pomysły, których wcześniej nie widziałem!
Piotr99

7
Dzięki za jednoczesną wskazówkę „zdefiniuj i wykonaj”! Myślę, że warto zauważyć, że działa to jako kontrowersja dla ifoświadczeń: if ([i]{ for (char j : i) if (!isspace(j)) return false ; return true ; }()) // i is all whitespacezakładając, że ijeststd::string
Blacklight Shining

74
Więc po to wyraz prawny: [](){}();.
nobar

8
Ugh! (lambda: None)()Składnia Pythona jest o wiele bardziej czytelna.
dan04

9
@nobar - masz rację, pomyliłem się. To jest legalne (tym razem przetestowałem)main() {{{{((([](){{}}())));}}}}
Mark Lakata

38

Odpowiedzi

P: Co to jest wyrażenie lambda w C ++ 11?

Odp .: Pod maską jest to obiekt klasy generowanej automatycznie z przeciążeniem operatora () const . Taki obiekt nazywa się zamknięciem i jest tworzony przez kompilator. Ta koncepcja „zamknięcia” jest zbliżona do koncepcji wiązania z C ++ 11. Ale lambda zazwyczaj generują lepszy kod. Połączenia przez zamknięcia umożliwiają pełne wstawianie.

P: Kiedy miałbym go użyć?

Odp .: Aby zdefiniować „prostą i małą logikę” i poprosić kompilatora o wykonanie generacji z poprzedniego pytania. Dajesz kompilatorowi kilka wyrażeń, które mają znajdować się wewnątrz operatora (). Wszystkie inne kompilatory rzeczy wygenerują dla Ciebie.

P: Jakiego rodzaju problemy rozwiązują, które nie były możliwe przed ich wprowadzeniem?

Odp .: Jest to rodzaj cukru składniowego, takiego jak przeciążanie operatorów zamiast funkcji do niestandardowych operacji dodawania, subrtact ... Zapisuje jednak więcej wierszy niepotrzebnego kodu, aby zawrzeć 1-3 linie prawdziwej logiki w niektórych klasach itp.! Niektórzy inżynierowie uważają, że jeśli liczba wierszy jest mniejsza, wówczas istnieje mniejsze prawdopodobieństwo popełnienia błędów (tak też uważam)

Przykład użycia

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

Dodatki dotyczące lambdas, nie objęte pytaniem. Zignoruj ​​tę sekcję, jeśli nie jesteś zainteresowany

1. Przechwycone wartości. Co możesz uchwycić

1.1 Możesz odwoływać się do zmiennej o statycznym czasie przechowywania w lambdas. Wszyscy zostali schwytani.

1.2 Możesz użyć lambda do przechwytywania wartości „według wartości”. W takim przypadku przechwycone zmienne zostaną skopiowane do obiektu funkcji (zamknięcie).

[captureVar1,captureVar2](int arg1){}

1.3 Możesz przechwycić jako odniesienie. & - w tym kontekście oznacza odniesienie, a nie wskaźniki.

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

1.4 Istnieje notacja do przechwytywania wszystkich zmiennych niestatycznych według wartości lub referencji

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

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

1.5 Istnieje notacja do przechwytywania wszystkich zmiennych niestatycznych według wartości lub referencji i określania smth. więcej. Przykłady: Przechwyć wszystkie zmienne statyczne według wartości, ale poprzez odniesienie przechwyć Param2

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

Przechwyć wszystkie zmienne statyczne przez odniesienie, ale przez przechwycenie wartości Param2

[&,Param2](int arg1){} 

2. Odliczenie typu zwrotu

2.1 Typ powrotu lambda można wywnioskować, jeśli lambda jest jednym wyrażeniem. Lub możesz to wyraźnie określić.

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

Jeśli lambda ma więcej niż jedno wyrażenie, to typ zwracany musi być określony za pomocą końcowego typu zwracanego. Podobną składnię można również zastosować do funkcji automatycznych i funkcji składowych

3. Przechwycone wartości. Czego nie można uchwycić

3.1 Można przechwytywać tylko zmienne lokalne, a nie zmienną składową obiektu.

4. Konwersje

4.1 !! Lambda nie jest wskaźnikiem funkcji i nie jest funkcją anonimową, ale lambdy bez przechwytywania można pośrednio przekonwertować na wskaźnik funkcji.

ps

  1. Więcej informacji na temat gramatyki lambda można znaleźć w Roboczym szkicu dla języka programowania C ++ # 337, 16.01.2012, 5.1.2. Wyrażenia lambda, s. 88

  2. W C ++ 14 dodano dodatkową funkcję o nazwie „przechwytywanie init”. Pozwala na arbitrażowe zgłaszanie członków danych zamknięcia:

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

To [&,=Param2](int arg1){}nie wydaje się poprawna składnia. Prawidłowa forma to[&,Param2](int arg1){}
GetFree

Dzięki. Najpierw próbowałem skompilować ten fragment kodu. I wydaje się dziwna asymetria w dozwolonych modyfikatorach na liście przechwytywania // g ++ -std = c ++ 11 main.cpp -o test_bin; ./test_bin #include <stdio.h> int main () {#if 1 {int param = 0; auto f = [=, & param] (int arg1) mutable {param = arg1;}; f (111); printf ("% i \ n", param); } #endif #if 0 {int param = 0; auto f = [&, = param] (int arg1) mutable {param = arg1;}; f (111); printf ("% i \ n", param); } #endif return 0; }
bruziuz

Wygląda na to, że nowa linia nie jest obsługiwana w komentarzu. Następnie otworzyłem wyrażenia Lambda 5.1.2, s. 88, „Szkic roboczy, standard dla języka programowania C ++”, numer dokumentu: # 337, 16.01.2012. I przyjrzał się składni gramatycznej. I masz rację. Nie ma czegoś takiego jak przechwytywanie za pomocą „= arg”
bruziuz 16.04.17

Wielkie dzięki, naprawiłem to w opisie, a także zdobyłem nową wiedzę na ten temat.
bruziuz 16.04.17

16

Funkcja lambda to anonimowa funkcja tworzona bezpośrednio. Może przechwytywać zmienne, jak niektórzy wyjaśnili (np. Http://www.stroustrup.com/C++11FAQ.html#lambda ), ale istnieją pewne ograniczenia. Na przykład jeśli istnieje taki interfejs zwrotny,

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

możesz napisać funkcję na miejscu, aby użyć jej tak jak tej, którą zastosowano poniżej:

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

Ale nie możesz tego zrobić:

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

z powodu ograniczeń w standardzie C ++ 11. Jeśli chcesz używać przechwytywania, musisz polegać na bibliotece i

#include <functional> 

(lub inny algorytm podobny do biblioteki STL, aby uzyskać go pośrednio), a następnie pracować ze std :: function zamiast przekazywać normalne funkcje jako parametry takie jak to:

#include <functional>
void apply(std::function<void(int)> f) {
    f(10);
    f(20);
    f(30);
}
void output(int width) {
    int col;
    apply([width,&col](int data) {
        cout << data << ((++col % width) ? ' ' : '\n');
    });
}

1
Powodem jest to, że lambda może przekształcić się we wskaźnik funkcji tylko wtedy, gdy nie ma przechwytywania. gdyby applybył szablonem, który akceptuje funktor, działałby
sp2danny,

1
Problem polega jednak na tym, że jeśli Apply jest istniejącym interfejsem, możesz nie mieć luksusu, że możesz go zadeklarować inaczej niż zwykłą starą funkcję. Standard mógłby zostać zaprojektowany w taki sposób, aby umożliwić generowanie nowego wystąpienia zwykłej starej funkcji za każdym razem, gdy takie wyrażenie lambda jest wykonywane, z wygenerowanymi zakodowanymi odniesieniami do przechwyconych zmiennych. Wygląda na to, że funkcja lambda jest generowana w czasie kompilacji. Są też inne konsekwencje. np. jeśli zadeklarujesz zmienną statyczną, nawet jeśli ponownie ocenisz wyrażenie lambda, nie otrzymasz nowej zmiennej statycznej.
Ted

1
wskaźnik funkcji jest często przeznaczony do zapisania, a przechwytywanie lambdas może wyjść poza zakres. że tylko
lambdy bez

1
Nadal musisz zwracać uwagę na to, że zmienne stosu są zwalniane z tego samego powodu. Zobacz blogs.msdn.com/b/nativeconcurrency/archive/2012/01/29/ ... Przykład, który napisałem z danymi wyjściowymi i zastosowaniem, został napisany, tak aby zamiast tego dozwolone i używane były wskaźniki funkcji, działałyby również. Kol pozostaje przydzielony do momentu zakończenia wszystkich wywołań funkcji z Apply. Jak przepisałbyś ten kod do pracy przy użyciu istniejącego interfejsu wprowadzania? Czy użyłbyś zmiennych globalnych lub statycznych, czy też bardziej niejasnej transformacji kodu?
Ted

1
a może po prostu masz na myśli, że wyrażenia lambda są wartościami wartościowymi, a zatem tymczasowymi, ale kod pozostaje stały (singleton / static), aby można go było wywołać w przyszłości. W takim przypadku być może funkcja powinna pozostać przydzielona, ​​dopóki jej przechwytywania przydzielone przez stos pozostaną przydzielone. Oczywiście może być bałagan odwijający, jeśli na przykład wiele wariantów funkcji jest przydzielonych w pętli.
Ted

12

Jednym z najlepszych wyjaśnień lambda expressionjest autor C ++ Bjarne Stroustrup w swoim ***The C++ Programming Language***rozdziale książkowym 11 ( ISBN-13: 978-0321563842 ):

What is a lambda expression?

Ekspresja N , czasami określane mianem lambda funkcji lub (niesłusznie ściśle mówiąc, ale potocznie) w lambda , to uproszczony notacja dla definiowania i stosując anonimowego obiektu funkcji . Zamiast definiować nazwaną klasę za pomocą operatora (), później tworząc obiekt tej klasy i wreszcie wywołując ją, możemy użyć skrótu.

When would I use one?

Jest to szczególnie przydatne, gdy chcemy przekazać operację jako argument do algorytmu. W kontekście graficznych interfejsów użytkownika (i innych miejsc) takie operacje są często nazywane wywołaniami zwrotnymi .

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

Tutaj wydaje mi się, że każdą akcję wykonaną za pomocą wyrażenia lambda można rozwiązać bez nich, ale z dużo większym kodem i znacznie większą złożonością. Wyrażenie lambda jest to sposób optymalizacji kodu i sposób uczynienia go bardziej atrakcyjnym. Jak smutne przez Stroustup:

skuteczne sposoby optymalizacji

Some examples

poprzez wyrażenie lambda

void print_modulo(const vector<int>& v, ostream& os, int m) // output v[i] to os if v[i]%m==0
{
    for_each(begin(v),end(v),
        [&os,m](int x) { 
           if (x%m==0) os << x << '\n';
         });
}

lub poprzez funkcję

class Modulo_print {
         ostream& os; // members to hold the capture list int m;
     public:
         Modulo_print(ostream& s, int mm) :os(s), m(mm) {} 
         void operator()(int x) const
           { 
             if (x%m==0) os << x << '\n'; 
           }
};

lub nawet

void print_modulo(const vector<int>& v, ostream& os, int m) 
     // output v[i] to os if v[i]%m==0
{
    class Modulo_print {
        ostream& os; // members to hold the capture list
        int m; 
        public:
           Modulo_print (ostream& s, int mm) :os(s), m(mm) {}
           void operator()(int x) const
           { 
               if (x%m==0) os << x << '\n';
           }
     };
     for_each(begin(v),end(v),Modulo_print{os,m}); 
}

jeśli potrzebujesz, możesz wymienić lambda expressionjak poniżej:

void print_modulo(const vector<int>& v, ostream& os, int m)
    // output v[i] to os if v[i]%m==0
{
      auto Modulo_print = [&os,m] (int x) { if (x%m==0) os << x << '\n'; };
      for_each(begin(v),end(v),Modulo_print);
 }

Lub załóż inną prostą próbkę

void TestFunctions::simpleLambda() {
    bool sensitive = true;
    std::vector<int> v = std::vector<int>({1,33,3,4,5,6,7});

    sort(v.begin(),v.end(),
         [sensitive](int x, int y) {
             printf("\n%i\n",  x < y);
             return sensitive ? x < y : abs(x) < abs(y);
         });


    printf("sorted");
    for_each(v.begin(), v.end(),
             [](int x) {
                 printf("x - %i;", x);
             }
             );
}

wygeneruje następny

0

1

0

1

0

1

0

1

0

1

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

[]- jest to lista przechwytywania lub lambda introducer: jeśli lambdasnie wymagają dostępu do lokalnego środowiska, możemy z niego skorzystać.

Cytat z książki:

Pierwszym znakiem wyrażenia lambda jest zawsze [ . Wprowadzający lambda może przybierać różne formy:

[] : pusta lista przechwytywania. Oznacza to, że w ciele lambda nie można używać żadnych lokalnych nazw z otaczającego kontekstu. W przypadku takich wyrażeń lambda dane są uzyskiwane z argumentów lub zmiennych nielokalnych.

[&] : domyślnie przechwytywanie przez odniesienie. Można używać wszystkich lokalnych nazw. Dostęp do wszystkich zmiennych lokalnych można uzyskać przez odniesienie.

[=] : domyślnie przechwytywanie według wartości. Można używać wszystkich lokalnych nazw. Wszystkie nazwy odnoszą się do kopii zmiennych lokalnych pobranych w punkcie wywołania wyrażenia lambda.

[lista przechwytywania]: jawne przechwytywanie; lista przechwytywania to lista nazw zmiennych lokalnych, które mają zostać przechwycone (tj. zapisane w obiekcie) przez odniesienie lub wartość. Zmienne o nazwach poprzedzonych znakiem & są przechwytywane przez odniesienie. Inne zmienne są przechwytywane według wartości. Lista przechwytywania może również zawierać to i nazwy, po których następuje ... jako elementy.

[&, capture-list] : domyślnie przechwytuj przez odniesienie wszystkie zmienne lokalne o nazwach niewymienionych na liście. Lista przechwytywania może to zawierać. Nazw wymienionych na liście nie można poprzedzać &. Zmienne nazwane na liście przechwytywania są przechwytywane według wartości.

[= lista przechwytywania] : domyślnie przechwytuj według wartości wszystkie zmienne lokalne o nazwach niewymienionych na liście. Lista przechwytywania nie może tego zawierać. Wymienione nazwy muszą być poprzedzone &. Zmienne wymienione na liście przechwytywania są przechwytywane przez odniesienie.

Zauważ, że nazwa lokalna poprzedzona znakiem & jest zawsze przechwytywana przez odniesienie, a nazwa lokalna nieprzedstawiona przez & jest zawsze przechwytywana przez wartość. Tylko przechwytywanie przez odniesienie pozwala modyfikować zmienne w środowisku wywołującym.

Additional

Lambda expression format

wprowadź opis zdjęcia tutaj

Dodatkowe referencje:


Ładne wyjaśnienie. Używając pętli opartych na zakresie, możesz uniknąć lambd i skrócić kodfor (int x : v) { if (x % m == 0) os << x << '\n';}
Dietrich Baumgarten

2

Odkryłem, że jednym praktycznym zastosowaniem jest ograniczenie kodu płyty kotła. Na przykład:

void process_z_vec(vector<int>& vec)
{
  auto print_2d = [](const vector<int>& board, int bsize)
  {
    for(int i = 0; i<bsize; i++)
    {
      for(int j=0; j<bsize; j++)
      {
        cout << board[bsize*i+j] << " ";
      }
      cout << "\n";
    }
  };
  // Do sth with the vec.
  print_2d(vec,x_size);
  // Do sth else with the vec.
  print_2d(vec,y_size);
  //... 
}

Bez lambda może być konieczne zrobienie czegoś w różnych bsizeprzypadkach. Oczywiście możesz stworzyć funkcję, ale co jeśli chcesz ograniczyć użycie w ramach funkcji użytkownika duszy? natura lambda spełnia ten wymóg i używam go w tym przypadku.


2

Lambda w c ++ są traktowane jako „dostępna funkcja on the go”. tak, dosłownie w podróży, definiujesz to; Użyj tego; a po zakończeniu zakresu funkcji rodzica funkcja lambda zniknęła.

c ++ wprowadził go w c ++ 11 i wszyscy zaczęli go używać jak w każdym możliwym miejscu. przykład i czym jest lambda można znaleźć tutaj https://en.cppreference.com/w/cpp/language/lambda

Opiszę, czego nie ma, ale trzeba wiedzieć dla każdego programisty c ++

Lambda nie jest przeznaczona do stosowania wszędzie i każdej funkcji nie można zastąpić lambda. Nie jest to również najszybszy w porównaniu do normalnej funkcji. ponieważ ma pewne koszty ogólne, którymi musi zająć się lambda.

z pewnością pomoże w niektórych przypadkach zmniejszyć liczbę linii. można go zasadniczo użyć do sekcji kodu, która jest wywoływana w tej samej funkcji raz lub więcej razy i ten fragment kodu nie jest potrzebny nigdzie indziej, aby można było utworzyć dla niego samodzielną funkcję.

Poniżej znajduje się podstawowy przykład lambda i tego, co dzieje się w tle.

Kod użytkownika:

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

  endGame(4,5);

  return 0;

}

Jak kompiluje to:

int main()
{
  int member = 10;

  class __lambda_6_18
  {
    int member;
    public: 
    inline /*constexpr */ int operator()(int a, int b) const
    {
      return a + b + member;
    }

    public: __lambda_6_18(int _member)
    : member{_member}
    {}

  };

  __lambda_6_18 endGame = __lambda_6_18{member};
  endGame.operator()(4, 5);

  return 0;
}

tak jak widać, jaki rodzaj narzutu dodaje, gdy go używasz. więc nie warto używać ich wszędzie. można go stosować w miejscach, w których mają one zastosowanie.


tak, dosłownie w podróży, definiujesz to; Użyj tego; a po zakończeniu zakresu funkcji rodzica funkcja lambda zniknie . Co jeśli funkcja zwróci lambda do programu wywołującego?
Nawaz

1
Nie jest to również najszybszy w porównaniu do normalnej funkcji. ponieważ ma pewne koszty ogólne, którymi musi zająć się lambda. Czy kiedykolwiek rzeczywiście uruchomić żadnego odniesienia do poparcia tego twierdzenia ? Przeciwnie, szablony lambda + często generują najszybszy możliwy kod.
Nawaz

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.