Лямбда-вирази у С++
Лямбда-вираз у С++ — анонімна функція, яка підтримує стан між викликами і може отримати доступ до змінних зі своєї області видимості. Використовуючи лямбда-вирази, можна оголошувати функції в будь-якому місці коду.
Лямбда-вирази підтримуються й іншими (окрім С++) мовами програмування, такими, як: 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
Посилання
- MSDN
- Лямбда-вирази, auto та static_assert: C++0x можливості в VC10
- Achim Jung, A Short Introduction to the Lambda Calculus-(PDF)
- Raúl Rojas, A Tutorial Introduction to the Lambda Calculus(англ.) -(PDF)
- A Brief Look at C++0x by Bjarne Stroustrup (англ.)
- Lambda Week: Syntax changes, C++11 to C++20