Що таке лямбда-вираз у C ++ 11?


1485

Що таке лямбда-вираз у C ++ 11? Коли я використовую його? Який клас проблем вони вирішили, що було неможливо до їх введення?

Кілька прикладів та випадків використання було б корисно.


14
Я бачив випадок, коли лямбда був дуже корисним: колега зі мною робив код, який має мільйони ітерацій для вирішення проблеми оптимізації простору. Алгоритм був набагато швидшим при використанні лямбда, ніж належна функція! Компілятор - Visual C ++ 2013.
sergiol

Відповіді:


1489

Проблема

C ++ включає такі корисні загальні функції, як std::for_eachі std::transform, що може бути дуже зручно. На жаль, вони також можуть бути досить громіздкими у використанні, особливо якщо функтор, який ви хочете застосувати, унікальний для певної функції.

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

Якщо ви використовуєте лише fодин раз і в цьому конкретному місці, здається, зайвим є написання цілого класу, щоб зробити щось тривіальне і одне.

У C ++ 03 ви можете спокуситись написати щось на зразок наступного, щоб зберегти функціонера локальним:

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

однак це не дозволено, fне може бути передано функції шаблону в C ++ 03.

Нове рішення

C ++ 11 представляє лямбдаси, які дозволяють написати вбудований, анонімний функтор для заміни struct f. Для невеликих простих прикладів це може бути більш чистим для читання (він зберігає все на одному місці) і потенційно простішим у підтримці, наприклад, у найпростішому вигляді:

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

Функції лямбда - це лише синтаксичний цукор для анонімних функціонерів.

Типи повернення

У простих випадках для вас виводиться тип повернення лямбда, наприклад:

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

однак, коли ви почнете писати складніші лямбда, ви швидко зустрінете випадки, коли компілятор не може визначити тип повернення, наприклад:

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

Щоб вирішити це рішення, ви можете чітко вказати тип повернення для функції лямбда, використовуючи -> 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;
            }
        });
}

"Захоплення" змінних

Поки ми не використовували нічого, крім того, що було передано лямбда всередині нього, але ми також можемо використовувати й інші змінні в межах лямбда. Якщо ви хочете отримати доступ до інших змінних, ви можете використовувати пункт захоплення ( []вираз), який до цих пір не використовувався в цих прикладах, наприклад:

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

Ви можете захопити з допомогою як посилання і значення, яке можна задати з допомогою &і =відповідно:

  • [&epsilon] захоплення посиланням
  • [&] фіксує всі змінні, використовувані в лямбда за посиланням
  • [=] фіксує всі значення, використовувані в лямбда за значенням
  • [&, epsilon] фіксує змінні типу [&], але epsilon за значенням
  • [=, &epsilon] фіксує змінні типу [=], але epsilon за посиланням

Сформований operator()це constза замовчуванням, з підтекстом , що захоплює буде constпри зверненні до них за замовчуванням. Це призводить до того, що кожен виклик з одним і тим же входом дасть однаковий результат, однак ви можете відзначити лямбда якmutable просити, щоб те, operator()що виробляється, не було const.


9
@Yakk ви потрапили в пастку. лямбди без захоплення мають неявне перетворення у покажчики типу функцій. функція перетворення constзавжди ...
Йоханнес Шауб - ліб

2
@ JohannesSchaub-litb ох підлий - і це трапляється, коли ви викликаєте ()- він передається як лямбда-аргумент із нульовим аргументом, але, оскільки () constне відповідає лямбда, він шукає конверсію типу, яка дозволяє це, що включає неявний припис -to-function-pointer, а потім викликає це! Підлий!
Якк - Адам Невраумон

2
Цікаво - спочатку я вважав, що лямбди - це анонімні функції, а не функтори, і був розгублений у тому, як працюють знімки .
користувач253751

49
Якщо ви хочете використовувати лямбда в якості змінних у вашій програмі, ви можете використовувати: std::function<double(int, bool)> f = [](int a, bool b) -> double { ... }; Але зазвичай ми дозволяємо компілятору вивести тип: auto f = [](int a, bool b) -> double { ... }; (і не забудьте #include <functional>)
Еверт Гейлен

11
Я думаю, не всі розуміють, чому return d < 0.00001 ? 0 : d;гарантовано повертається подвійне, коли один з операндів є цілою константою (це через неявне правило просування оператора ?:, де 2-й і 3-й операнди врівноважуються один проти одного за допомогою звичайної арифметики конверсії незалежно від того, яку саме обрали). Якщо змінити 0.0 : d, можливо, це полегшить розуміння прикладу.
Лундінь

828

Що таке лямбда-функція?

Концепція лямбда-функції C ++ бере свій початок в обчисленні лямбда та функціональному програмуванні. Лямбда - це неназвана функція, яка корисна (у фактичному програмуванні, а не в теорії) для коротких фрагментів коду, які неможливо повторно використовувати і не варто називати.

У C ++ функція лямбда визначається так

[]() { } // barebone lambda

або у всій красі

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

[]- це список захоплення, список ()аргументів та {}тіло функції.

Список захоплення

Список захоплення визначає, що ззовні лямбда має бути доступним усередині функціонального тіла та як. Це може бути будь-який:

  1. значення: [x]
  2. посилання [& x]
  3. будь-яка змінна, яка зараз знаходиться в області застосування за посиланням [&]
  4. те саме, що 3, але за значенням [=]

Ви можете змішати будь-що з перерахованого вище у списку, розділеному комами [x, &y].

Список аргументів

Список аргументів такий же, як і в будь-якій іншій функції C ++.

Орган функції

Код, який буде виконуватися, коли фактично викликається лямбда.

Відшкодування типу повернення

Якщо лямбда має лише одне твердження повернення, тип повернення може бути пропущений і має неявний тип decltype(return_statement).

Змінні

Якщо лямбда позначена мутаційною (наприклад []() mutable { }), дозволяється мутувати значення, які були захоплені за значенням.

Використовуйте випадки

Бібліотека, визначена стандартом ISO, значною мірою користується лямбдами та підвищує зручність використання декількох барів, оскільки тепер користувачі не повинні захаращувати свій код невеликими функторами в деякій доступній області.

C ++ 14

У С ++ 14 лямбд були розширені різними пропозиціями.

Ініціалізовані захоплення лямбда

Елемент списку захоплення тепер може бути ініціалізований =. Це дозволяє перейменувати змінні та фіксувати переміщенням. Приклад, взятий із стандарту:

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.

та один із Вікіпедії, який показує, як зробити знімок за допомогою std::move:

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

Родові лямбдахи

Лямбди тепер можуть бути загальними ( autoбуло б рівнозначно Tтут, якби Tаргумент шаблону типу був десь у навколишньому просторі):

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

Поліпшення відрахування типу повернення

C ++ 14 дозволяє виводити типи повернення для кожної функції і не обмежує її функціями форми return expression;. Це поширюється і на лямбда.


2
У вашому прикладі для ініціалізованої лямбда-фіксації вище, чому ви закінчуєте функцію "ламба" на () ;? Це виглядає як [] () {} (); замість [](){};. Також не повинно значення х бути 5?
Рамакришнан Каннан

6
@RamakrishnanKannan: 1) () є, щоб викликати лямбда відразу після визначення її і дати y її повернене значення. Змінна y - ціле число, а не лямбда. 2) Ні, x = 5 є локальним для лямбда (захоплення за значенням, яке, як буває, має те саме ім'я, що і змінна зовнішня область x), і потім повертається x + 2 = 5 + 2. Перепризначення зовнішньої змінної x відбувається через посилання r:, r = &x; r += 2;але це відбувається з початковим значенням 4.
Vee

167

Ламбда-вирази зазвичай використовуються для інкапсуляції алгоритмів, щоб вони могли бути передані іншій функції. Однак можна виконати лямбда відразу після визначення :

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

функціонально еквівалентний

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

Це робить лямбда-вирази потужним інструментом для рефакторингу складних функцій . Ви починаєте, загортаючи розділ коду в лямбда-функцію, як показано вище. Процес явної параметризації може бути виконаний поступово з проміжним тестуванням після кожного кроку. Після того, як блок коду буде повністю параметризований (як показано видаленням &), ви можете перемістити код у зовнішнє місце та зробити його нормальною функцією.

Так само ви можете використовувати лямбда-вирази для ініціалізації змінних на основі результату алгоритму ...

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

Як розділення логіки вашої програми , ви навіть можете вважати корисним передавати лямбда-вираз як аргумент іншому лямбда-виразу ...

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

Лямбда-вирази також дозволяють створювати названі вкладені функції , що може бути зручним способом уникнути дублювання логіки. Використання названих лямбдаз також має тенденцію бути дещо легшими для очей (порівняно з анонімними вбудованими лямбдатами) при передачі нетривіальної функції в якості параметра іншій функції. Примітка: не забудьте крапку з комою після закриття фігурної дужки.

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

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

Якщо наступне профілювання виявить значну ініціалізацію накладних витрат для об'єкта функції, ви можете вибрати цю функцію як звичайну функцію.


11
Ви зрозуміли, що це питання задавали 1,5 роки тому і що останній захід був майже 1 рік тому? У будь-якому випадку ви вносите цікаві ідеї, яких я раніше не бачив!
Piotr99

7
Дякуємо за одночасне визначення та виконання підказки! Я думаю, що варто зауважити, що це працює як контидіон для ifвисловлювань: if ([i]{ for (char j : i) if (!isspace(j)) return false ; return true ; }()) // i is all whitespaceприпустимо, що iцеstd::string
Blacklight Shining

74
Таким чином, наступне є вираженням: [](){}();.
nobar

8
Тьфу! (lambda: None)()Синтаксис Python настільки розбірливіший.
dan04

9
@nobar - ти маєш рацію, я неправильно вказав. Це законно (я перевірив це цього разу)main() {{{{((([](){{}}())));}}}}
Марк Лаката

38

Відповіді

Питання: Що таке лямбда-вираз у C ++ 11?

A: Під капотом це об'єкт автогенерованого класу з const оператора перевантаження () . Такий об’єкт називається закриттям і створюється компілятором. Ця концепція "закриття" близька до концепції зв'язування з C ++ 11. Але лямбди зазвичай генерують кращий код. А дзвінки через закриття дозволяють повністю вбудовуватися.

Питання: Коли я використовую його?

A: Визначити "просту і малу логіку" і попросити компілятора виконати генерацію з попереднього питання. Ви даєте компілятору деякі вирази, якими ви хочете бути всередині оператора (). Усі інші компілятори речей генерують для вас.

Питання: Який клас проблем вони вирішили, що було неможливо до їх введення?

Відповідь: Це якийсь синтаксичний цукор, як оператори, що перевантажують замість функцій для спеціальних операцій додавання, підкорення ... Але це заощаджує більше рядків непотрібного коду, щоб зафіксувати 1-3 рядки реальної логіки для деяких класів тощо! Деякі інженери вважають, що якщо кількість рядків менша, то менше шансів помилитися в цьому (я теж думаю, що так)

Приклад використання

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

Екстра про лямбда, не охоплені питаннями. Ігноруйте цей розділ, якщо вас не цікавить

1. Захоплені цінності. Що ви можете захопити

1.1. Ви можете посилатися на змінну зі статичною тривалістю зберігання в лямбдах. Усі вони потрапляють у полон.

1.2. Ви можете використовувати лямбда для отримання значень "за значенням". У такому випадку захоплені vars будуть скопійовані в об’єкт функції (закриття).

[captureVar1,captureVar2](int arg1){}

1.3. Ви можете зафіксувати посилання. & - в цьому контексті означають посилання, а не покажчики.

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

1.4. Існує позначення для захоплення всіх нестатичних змін за значенням або за посиланням

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

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

1.5. Існує позначення для захоплення всіх нестатичних змін за значенням або за посиланням та вказування smth. більше. Приклади: Захоплення всіх нестатичних vars за значенням, але шляхом відліку Param2

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

Захоплення всіх нестатичних vars за посиланням, а за допомогою захоплення значення Param2

[&,Param2](int arg1){} 

2. Відрахування типу повернення

2.1. Тип повернення лямбда можна вивести, якщо лямбда - це один вираз. Або ви можете чітко вказати це.

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

Якщо лямбда має більше одного виразу, то тип повернення повинен бути вказаний через зворотний тип повернення. Також подібний синтаксис може бути застосований до автоматичних функцій та функцій членів

3. Захоплені значення. Те, що ти не можеш захопити

3.1. Ви можете захоплювати лише локальні vars, а не змінні елементи об'єкта.

4. Конверсії

4.1 !! Лямбда не є покажчиком функції і не є анонімною функцією, але лямбдаси без захоплення можуть бути неявно перетворені у покажчик функції.

пс

  1. Більше про інформацію про грамматику лямбда можна знайти в робочому проекті мови програмування C ++ # 337, 2012-01-16, 5.1.2. Лямбдаські вирази, с.88

  2. У C ++ 14 додана додаткова функція, яка названа "init capture". Це дозволяє виконувати довільне оголошення членів даних про закриття:

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

Це , [&,=Param2](int arg1){}здається, не дійсні синтаксис. Правильна форма буде[&,Param2](int arg1){}
GetFree

Дякую. Спочатку я спробував скласти цей фрагмент. І здається дивним асиметрія у допустимих модифікаторах у списку захоплення // 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", парам); } #endif #if 0 {int param = 0; auto f = [&, = param] (int arg1) mutable {param = arg1;}; f (111); printf ("% i \ n", парам); } #endif return 0; }
bruziuz

Здається, що новий рядок у коментарі не підтримується. Потім я відкрив 5.1.2 лямбдаські вирази, стор.88, "Робочий проект, стандарт для мови програмування C ++", номер документа: № 337, 2012-01-16. І вивчив граматичний синтаксис. І ти маєш рацію. Не існує такого поняття, як захоплення через "= arg"
bruziuz

Велике спасибі, зафіксувавши це в описі, а також отримайте до нього нові знання.
bruziuz

16

Функція лямбда - це анонімна функція, яку ви створюєте в режимі рядка. Він може фіксувати змінні, як пояснили деякі (наприклад, http://www.stroustrup.com/C++11FAQ.html#lambda ), але є деякі обмеження. Наприклад, якщо є такий інтерфейс зворотного виклику,

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

ви можете написати функцію на місці, щоб використовувати її, як та, яка передана для подання нижче:

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

Але ви не можете цього зробити:

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

через обмеження в стандарті C ++ 11. Якщо ви хочете використовувати захоплення, вам потрібно покластися на бібліотеку та

#include <functional> 

(або якусь іншу бібліотеку STL, наприклад алгоритм, щоб отримати це побічно), а потім працювати з std :: функцією замість передачі звичайних функцій як таких параметрів:

#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
причина полягає в тому, що лямбда може перетворитись на функціональний покажчик, лише якщо вона не має захоплення. якби applyшаблон, який прийняв функтор, він би спрацював
sp2danny

1
Але проблема полягає в тому, що якщо застосувати це вже існуючий інтерфейс, можливо, у вас не буде розкоші бути в змозі оголосити його інакше, ніж звичайна стара функція. Стандарт міг бути розроблений таким чином, щоб дозволити генерувати новий екземпляр простої старої функції кожного разу, коли виконується такий лямбда-вираз із згенерованими жорстко кодованими посиланнями на захоплені змінні. Здається, лямбда-функція створюється під час компіляції. Є й інші наслідки. Наприклад, якщо ви оголошуєте статичну змінну, навіть якщо ви переоцінюєте лямбда-вираз, ви не отримаєте нову статичну змінну.
Тед

1
вказівник функції часто призначений для збереження, і захоплення лямбда може вийти за межі сфери застосування. що лише без лямбда-захоплення перетворити на функціональні покажчики було
задумано

1
Вам все одно доведеться звертати увагу на те, що змінні стека розміщуються з тієї ж причини в будь-якому випадку. Дивіться blogs.msdn.com/b/nativeconcurrency/archive/2012/01/29/… Приклад, який я написав з результатом і застосувати, написаний так, що якщо замість них дозволені та використані вказівники, вони також працюватимуть. Кол залишається виділеним до тих пір, поки всі функціональні дзвінки з програми не будуть закінчені. Як би ви переписали цей код на роботу за допомогою існуючого інтерфейсу застосувати? Чи могли б ви використати глобальні або статичні змінні, або дещо незрозуміле перетворення коду?
Тед

1
або, можливо, ви просто маєте на увазі, що лямбда-вирази є значеннями і, отже, тимчасові, але код залишається постійним (однотонний / статичний), щоб його можна було викликати в майбутньому. У такому випадку, можливо, функція повинна залишатися виділеною до тих пір, поки її присвоєні стеком захоплення залишаються виділеними. Звичайно, це може заплутатися, якщо, наприклад, багато варіантів функції виділяються в циклі.
Тед

12

Один з кращих пояснення lambda expressionдається від автора C ++ Бьерн Страуструп в своїй книзі ***The C++ Programming Language***главі 11 ( ISBN-13: 978-0321563842 ):

What is a lambda expression?

Лямбда - вираз , іноді також називають лямбда - функції або (строго кажучи , неправильно, але просторіччі) як лямбда , є спрощеною позначення для визначення і з допомогою анонімного об'єкта функції . Замість визначення названого класу з оператором (), згодом створення об'єкта цього класу і, нарешті, виклик до нього, ми можемо використовувати скорочення.

When would I use one?

Це особливо корисно, коли ми хочемо передати операцію як аргумент алгоритму. У контексті графічних інтерфейсів користувача (та інших) такі операції часто називають зворотними .

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

Тут я здогадуюсь, що кожна дія, зроблена з лямбда-виразом, може бути вирішена без них, але з набагато більшим кодом та значно більшою складністю. Ламбда-вираз - це спосіб оптимізації вашого коду та спосіб зробити його більш привабливим. Як сумно від Stroustup:

ефективні способи оптимізації

Some examples

через лямбда-вираз

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

або за допомогою функції

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

або навіть

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

якщо вам потрібно, ви можете назвати lambda expression:

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

Або припустимо інший простий зразок

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

буде генерувати далі

0

1

0

1

0

1

0

1

0

1

0 сортинг - 1; х - 3; х - 4; х - 5; х - 6; х - 7; х - 33;

[]- це список захоплення або lambda introducer: якщо lambdasне потребує доступу до їх локального середовища, ми можемо ним скористатися.

Цитата з книги:

Перший символ лямбдаського виразу завжди [ . Вступник лямбда може приймати різні форми:

[] : порожній список захоплення. Це означає, що в тілі лямбда не можна використовувати локальні назви з навколишнього контексту. Для таких лямбда-виразів дані отримують з аргументів або з нелокальних змінних.

[&] : неявна фіксація шляхом посилання. Можна використовувати всі локальні назви. До всіх локальних змінних можна отримати посилання.

[=] : неявна фіксація за значенням. Можна використовувати всі локальні назви. Усі імена відносяться до копій локальних змінних, взятих у точці виклику лямбда-виразу.

[список захоплення]: явне захоплення; список захоплення - це список імен локальних змінних, які підлягають захопленню (тобто зберігаються в об'єкті) за посиланням або за значенням. Змінні з іменами, які передують &, фіксуються за посиланням. Інші змінні фіксуються за значенням. Список захоплення також може містити це та імена, за якими слідує ... як елементи.

[&, capture-list] : неявно фіксує за допомогою посилання всі локальні змінні з іменами, не зазначеними у списку. Список захоплення може містити це. Перелічені імена не можуть передувати &. Змінні, названі у списку захоплення, фіксуються за значенням.

[=, улаштування списку] : неявно фіксує за значенням всі локальні змінні з іменами, не згаданими у списку. Список захоплення не може містити це. Переліченим іменам має передувати &. Змінні, названі у списку захоплення, фіксуються за посиланням.

Зауважте, що локальне ім'я, яке передує &, завжди фіксується посиланням, а локальне ім'я, яке не передує &, завжди захоплюється за значенням. Тільки захоплення за посиланням дозволяє змінювати змінні в середовищі виклику.

Additional

Lambda expression формат

введіть тут опис зображення

Додаткові посилання:


Приємне пояснення. Використовуючи діапазон для циклів, ви можете уникнути лямбда і скоротити кодfor (int x : v) { if (x % m == 0) os << x << '\n';}
Дітріх Баумгартен

2

Ну, я знайшов одне практичне використання - зменшити код пластини котла. Наприклад:

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

Без лямбда вам може знадобитися щось зробити для різних bsizeвипадків. Звичайно, ви можете створити функцію, але що робити, якщо ви хочете обмежити використання в межах функції користувача користувача soul? природа лямбда відповідає цій вимозі, і я використовую її для цього випадку.


2

Лямбда в c ++ трактується як функція "доступна в дорозі". так, це буквально на ходу, ви визначаєте це; використай це; і коли закінчується область батьківської функції, лямбда-функція зникла.

c ++ представив його в c ++ 11, і всі почали використовувати його як у будь-якому можливому місці. Приклад і що таке лямбда можна знайти тут https://en.cppreference.com/w/cpp/language/lambda

Я опишу те, чого немає, але важливо знати кожному програмісту на C ++

Ламбда не призначена для використання скрізь, і кожна функція не може бути замінена лямбда. Це також не найшвидше порівняння зі звичайною функцією. тому що у нього є накладні витрати, з якими потрібно обробляти лямбда.

це, безумовно, допоможе зменшити кількість рядків у деяких випадках. він в основному може бути використаний для розділу коду, який викликається в одній і тій же функції один раз або кілька разів, і цей фрагмент коду більше не потрібен ніде, щоб ви могли створити для нього окрему функцію.

Нижче наведено основний приклад лямбда та те, що відбувається на задньому плані.

Код користувача:

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

  endGame(4,5);

  return 0;

}

Як компілювати розширює його:

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

тож, як ви бачите, який тип накладних витрат додається, коли ви його використовуєте. тому не дуже гарно їх використовувати всюди. його можна використовувати в місцях, де вони застосовні.


так, це буквально на ходу, ви визначаєте це; використай це; і коли область батьківської функції закінчується, лямбда-функція відсутня .. що робити, якщо функція повертає лямбда-адресу абоненту?
Наваз

1
Це також не найшвидше порівняння зі звичайною функцією. тому що у нього є накладні витрати, з якими потрібно обробляти лямбда. Ви коли-небудь насправді виконували якісь еталони для підтримки цієї вимоги ? Навпаки, лямбда + шаблони часто створюють найшвидший код.
Наваз

1

Одна проблема, яку вона вирішує: Код простіший за лямбда для виклику в конструкторі, який використовує функцію вихідного параметра для ініціалізації члена const

Ви можете ініціалізувати члена const свого класу з викликом функції, яка встановлює його значення, повертаючи його вихід у якості вихідного параметра.


Це також можна зробити за допомогою простої функції, яка навіть відповідає прийнятому відповіді на запитання, з яким ви пов’язані.
SirGuy
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.