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

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

Лямбда-вирази підтримуються й іншими (окрім С++) мовами програмування, такими, як: 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

Див. також

Посилання

This article is issued from Wikipedia. The text is licensed under Creative Commons - Attribution - Sharealike. Additional terms may apply for the media files.