Лямбда-вирази у С++

Лямбда-вираз у С++ — анонімна функція, яка підтримує стан між викликами і може отримати доступ до змінних зі своєї області видимості. Використовуючи лямбда-вирази, можна оголошувати функції в будь-якому місці коду.

Лямбда-вирази підтримуються й іншими (окрім С++) мовами програмування, такими, як: C#, С, Python, PHP,Visual Basic .NET та іншими.

Про лямбда-виразиРедагувати

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

#include <algorithm>
#include <cmath>
void abssort(float* x, unsigned N) {
    std::sort(x, x + N, 
        // починається лямбда-вираз
        [](float a, float b) {
            return std::abs(a) < std::abs(b);
        });
}

Об'єкти функцій і лямбда-виразівРедагувати

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

Синтаксис лямбда-виразівРедагувати

Граматика лямбда-виразівРедагувати

Наступне формальне визначення виражає граматику зі стандарту ISO C++11 (елементи з opt у дужках є необов'язковими)

 lambda-introducer lambda-declarator(opt) compound-statement

Відповідні компоненти синаксису виражаються так:

lambda-introducer:

        [ lambda-capture(opt) ]

lambda-capture:

       capture-default
       capture-list
        capture-default , capture-list

capture-default:

       &
       =

capture-list:

        capture ... (opt)
        capture-list , capture ... (opt)

capture:

        ідентифікатор
        & identifier
       this

lambda-declarator:

        ( parameter-declaration-clause ) mutable(opt)

Властивості лямбда-виразівРедагувати

[expr]Редагувати

  • Пустий вираз фіксації: [] означає, що тіло лямбда-виразу не має доступу до змінних в зовнішній області видимості.
  • Елемент & як параметр вказує, що тіло лямбда-виразу має доступ до всіх змінних по ссилці, якщо явно не задано інше.
  • Елемент = як параметр вказує, що тіло лямбда-виразу має доступ до всіх змінних по значенню, якщо явно не задано інше.

Наприклад, якщо тіло лямбда-виразу має доступ до змінної total по посиланню, а до змінної factor по значенню, наступні вирази еквівалентні:

[&total, factor]
[factor, &total]
[&, factor]
[factor, &]
[=, &total]
[&total, =]

Часто люди припускаються таких помилок, зв'язаних з виразом фіксації:

struct S { void f(int i); };

void S::f(int i) {
    [&, i]{};    // OK
    [&, &i]{};   // ERROR: i preceded by & when & is the default
    [=, this]{}; // ERROR: this when = is the default
    [i, i]{};    // ERROR: i repeated
}

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

Список параметрів (lambda declarator) для лямбда-виразів є необов'язковим і нагадує список параметрів функцій. Лямбда-вираз також може приймати інший лямбда-вираз як параметр.

Специфікація виключних ситуаційРедагувати

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

	// throw_lambda_expression.cpp
	// compile with: /W4 /EHsc 
	int main() // C4297 expected
	{
	   []() throw() { throw 5; }();
	}

Значення, що повертаєтьсяРедагувати

Можна опустити вираз, що повертає значення у лямбда-виразі, якщо тіло лямбда-функції складається з одного return, або нічого не повертає. Якщо лямбда-вираз складається з одного оператора return, компілятор знаходить тип значення, що повертається з return-виразу, в противному випадку повертає значення void.

	auto x1 = [](int i){ return i; }; // OK: return type is int
	auto x2 = []{ return{ 1, 2 }; };  // ERROR: return type is void, deducing 
                                  		// return type from braced-init-list not valid

Тіло лямбда-виразуРедагувати

Тіло лямбда-виразу може мати доступ до таких змінних:

  • Параметри
  • Локально оголошені змінні
  • Елементи даних класу
  • Будь-яка статична змінна(наприклад глобальні функції)

Крім того лямбда-вираз може мати доступ до змінних, які воно фіксує ([]) з зовнішньої області видимості. Явно за допомогою [expr], або неявно. Тіло лямбда-виразу використовує значення за замовчуванням для доступу до неявно зафіксованих змінних. Наступний приклад демонструє фіксацію n явно по значенню, а m — неявно по посиланню.

#include <iostream>
using namespace std;

int main()
{
   int m = 0;
   int n = 0;
   [&, n] (int a) mutable { m = ++n + a; }(4);
   cout << m << endl << n << endl;
}

цей приклад виводить на консоль такий результат:

 5
 0

Джерела інформаціїРедагувати

  • Achim Jung, A Short Introduction to the Lambda Calculus
  • MSDN
  • Henk Barendregt, The Bulletin of Symbolic Logic, Volume 3, Number 2, June 1997. The Impact of the Lambda Calculus in Logic and Computer Science

Див. такожРедагувати

ПосиланняРедагувати