Namespaces
Variants

Lambda expressions (since C++11)

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

Создаёт closure (безымянный функциональный объект, способный захватывать переменные из области видимости).

Содержание

Синтаксис

Лямбда-выражения без явного списка параметров шаблона (возможно, негенерические)
[ captures  ] front-attr  (необязательно) ( params  ) specs  (необязательно) except  (необязательно)
back-attr  (необязательно) trailing  (необязательно) requires  (необязательно) contract-specs  (необязательно) { body }
(1)
[ captures  ] { body } (2) (до C++23)
[ captures  ] front-attr  (необязательно) trailing  (необязательно) contract-specs  (необязательно) { body } (2) (начиная с C++23)
[ captures  ] front-attr  (необязательно) except
back-attr  (необязательно) trailing  (необязательно) contract-specs  (необязательно) { body }
(3) (начиная с C++23)
[ captures  ] front-attr  (необязательно) specs except  (необязательно)
back-attr  (необязательно) trailing  (необязательно) contract-specs  (необязательно) { body }
(4) (начиная с C++23)
Лямбда-выражения с явным списком параметров шаблона (всегда обобщённые) (начиная с C++20)
[ захваты  ] < tparams  > t-requires  (необязательно)
front-attr  (необязательно) ( параметры  ) спецификаторы  (необязательно) except  (необязательно)
back-attr  (необязательно) trailing  (необязательно) requires  (необязательно) contract-specs  (необязательно) { тело }
(1)
[ захваты  ] < tparams  > t-requires  (необязательно) { тело } (2) (до C++23)
[ захваты  ] < tparams  > t-requires  (необязательно)
front-attr  (необязательно) trailing  (необязательно) contract-specs  (необязательно) { тело }
(2) (начиная с C++23)
[ захваты  ] < tparams  > t-requires  (необязательно) front-attr  (необязательно) except
back-attr  (необязательно) trailing  (необязательно) contract-specs  (необязательно) { тело }
(3) (начиная с C++23)
[ захваты  ] < tparams  > t-requires  (необязательно) front-attr  (необязательно) спецификаторы except  (необязательно)
back-attr  (необязательно) trailing  (необязательно) contract-specs  (необязательно) { тело }
(4) (начиная с C++23)
1) Лямбда-выражение со списком параметров.
2-4) Лямбда-выражение без списка параметров.
2) Простейший синтаксис. back-attr не может быть применен.
3,4) back-attr может применяться только при наличии любого из specs и except .

Объяснение

captures - Определяет сущности, которые должны быть захвачены .
tparams - Непустой разделённый запятыми список параметров шаблона , используемый для предоставления имён параметрам шаблона обобщённой лямбды (см. ClosureType::operator() ниже).
t-requires - Добавляет constraints к tparams .

Если t-requires заканчивается спецификатором атрибутов, то атрибуты в этом спецификаторе рассматриваются как атрибуты в front-attr .

(since C++23)
front-attr - (since C++23) Последовательность спецификаторов атрибутов применяется к operator ( ) типа замыкания (и, следовательно, атрибут [[ noreturn ]] может быть использован).
params - Список параметров для operator ( ) типа замыкания.

Может содержать явный объектный параметр .

(since C++23)
specs - Список следующих спецификаторов, каждый спецификатор разрешён не более одного раза в каждой последовательности.
Спецификатор Эффект
mutable Позволяет body изменять объекты, захваченные по копии, и вызывать их не-const функции-члены.
  • Не может использоваться, если присутствует явный параметр объекта.
(since C++23)
constexpr
(since C++17)
Явно указывает, что operator ( ) является constexpr функцией .
  • Если operator ( ) удовлетворяет всем требованиям constexpr функции, operator ( ) будет constexpr даже если constexpr отсутствует.
consteval
(since C++20)
Указывает, что operator ( ) является immediate функцией .
  • consteval и constexpr не могут быть указаны одновременно.
static
(since C++23)
Указывает, что operator ( ) является статической функцией-членом .
  • static и mutable не могут быть указаны одновременно.
  • Не может использоваться, если captures не пуст, или присутствует явный параметр объекта.
except - Предоставляет dynamic exception specification или (until C++20) спецификатор noexcept для operator ( ) типа замыкания.
back-attr - Последовательность спецификаторов атрибутов применяется к типу operator ( ) замыкающего типа (и поэтому атрибут [[ noreturn ]] не может быть использован).
trailing - -> ret , где ret указывает тип возвращаемого значения.
requires - (since C++20) Добавляет constraints к operator ( ) типа замыкания.
contract-specs - (since C++26) Список спецификаторов контракта функции для operator ( ) замыкающего типа.
body - Тело функции.


Если auto используется в качестве типа параметра или предоставлен явный список параметров шаблона (начиная с C++20) , лямбда-выражение является обобщённой лямбдой .

(начиная с C++14)

Переменная __func__ неявно определяется в начале тела функции , с семантикой, описанной здесь .

Тип замыкания

Лямбда-выражение является prvalue-выражением уникального безымянного не- union не- aggregate типа класса, известного как closure type , который объявляется (для целей ADL ) в наименьшей области видимости блока, класса или пространства имен, содержащей лямбда-выражение.

Тип замыкания является структурным типом тогда и только тогда, когда captures пуст.

(since C++20)

Тип замыкания имеет следующие члены, они не могут быть явно инстанцированы , явно специализированы , или (начиная с C++14) указаны в объявлении дружественной функции :

ClosureType:: operator()( params )

ret operator ( ) ( params ) { body }
(могут присутствовать static и const, см. ниже)
template < template - params >
ret operator ( ) ( params ) { body }
(начиная с C++14)
(обобщенная лямбда, могут присутствовать static и const, см. ниже)

Выполняет тело лямбда-выражения при вызове. При обращении к переменной обращается к её захваченной копии (для сущностей, захваченных по значению), или к исходному объекту (для сущностей, захваченных по ссылке).

Список параметров operator ( ) равен params если он предоставлен, в противном случае список параметров пуст.

Возвращаемый тип operator ( ) — это тип, указанный в trailing .

Если trailing не указан, возвращаемый тип operator ( ) автоматически выводится . [1]

Если ключевое слово mutable не было использовано в спецификаторах лямбда-выражения или отсутствует явный параметр объекта (начиная с C++23) , cv-квалификатор operator ( ) является const , и объекты, захваченные по значению, не могут быть изменены внутри этого operator ( ) . Явный квалификатор const не допускается. operator ( ) никогда не является виртуальным и не может иметь квалификатор volatile .

operator ( ) всегда является constexpr, если удовлетворяет требованиям constexpr-функции . Также является constexpr, если ключевое слово constexpr было использовано в спецификаторах лямбды.

(начиная с C++17)

operator ( ) является непосредственной функцией , если ключевое слово consteval было использовано в спецификаторах лямбды.

(начиная с C++20)

operator ( ) является статической функцией-членом , если ключевое слово static было использовано в спецификаторах лямбды.

operator ( ) является функцией-членом с явным объектом , если params содержит явный объектный параметр.

(начиная с C++23)


Для каждого параметра в params , тип которого указан как auto , в template-params добавляется изобретенный параметр шаблона в порядке их появления. Изобретенный параметр шаблона может быть parameter pack , если соответствующий член функции в params является function parameter pack.

// generic lambda, operator() is a template with two parameters
auto glambda = [](auto a, auto&& b) { return a < b; };
bool b = glambda(3, 3.14); // OK
// generic lambda, operator() is a template with one parameter
auto vglambda = [](auto printer)
{
    return [=](auto&&... ts) // generic lambda, ts is a parameter pack
    { 
        printer(std::forward<decltype(ts)>(ts)...);
        // nullary lambda (takes no parameters):
        return [=] { printer(ts...); };
    };
};
auto p = vglambda([](auto v1, auto v2, auto v3)
{
    std::cout << v1 << v2 << v3;
});
auto q = p(1, 'a', 3.14); // outputs 1a3.14
q();                      // outputs 1a3.14
(since C++14)


Если определение лямбды использует явный список параметров шаблона, этот список параметров шаблона используется с operator ( ) . Для каждого параметра в params , тип которого указан как auto , в конец этого списка параметров шаблона добавляется дополнительный изобретенный параметр шаблона:

// generic lambda, operator() is a template with two parameters
auto glambda = []<class T>(T a, auto&& b) { return a < b; };
// generic lambda, operator() is a template with one parameter pack
auto f = []<typename... Ts>(Ts&&... ts)
{
    return foo(std::forward<Ts>(ts)...);
};
(начиная с C++20)

Спецификация исключений except в лямбда-выражении применяется к operator ( ) .

Для целей name lookup , определения типа и значения this pointer и для доступа к нестатическим членам класса, тело operator ( ) closure type рассматривается в контексте лямбда-выражения.

struct X
{
    int x, y;
    int operator()(int);
    void f()
    {
        // контекст следующей лямбды - функция-член X::f
        [=]() -> int
        {
            return operator()(this->x + y); // X::operator()(this->x + (*this).y)
                                            // this имеет тип X*
        };
    }
};

Висячие ссылки

Если нессылочная сущность захватывается по ссылке, неявно или явно, и operator ( ) объекта замыкания вызывается после завершения времени жизни сущности, возникает неопределённое поведение. Замыкания C++ не продлевают время жизни объектов, захваченных по ссылке.

То же самое относится к времени жизни текущего * this объекта, захваченного через this .

  1. Хотя вывод типа возвращаемого значения функции был введен в C++14, его правило доступно для вывода типа возвращаемого значения лямбда-выражений в C++11.

ClosureType:: operator ret (*)( params )()

лямбда без захвата, не обобщенная
using F = ret ( * ) ( params ) ;
operator F ( ) const noexcept ;
(до C++17)
using F = ret ( * ) ( params ) ;
constexpr operator F ( ) const noexcept ;
(начиная с C++17)
лямбда без захвата, обобщенная
template < template - params > using fptr_t = /* см. ниже */ ;

template < template - params >

operator fptr_t < template - params > ( ) const noexcept ;
(начиная с C++14)
(до C++17)
template < template - params > using fptr_t = /* см. ниже */ ;

template < template - params >

constexpr operator fptr_t < template - params > ( ) const noexcept ;
(начиная с C++17)

Эта пользовательская функция преобразования определяется только в том случае, если лямбда-выражение не имеет захватов  и не имеет явного параметра объекта (since C++23) . Это публичная, constexpr, (since C++17) невиртуальная, неявная, const noexcept функция-член объекта замыкания.

Эта функция является immediate function если оператор вызова функции (или специализация, для обобщенных лямбд) является immediate function.

(since C++20)

Универсальное лямбда-выражение без захвата имеет шаблонную функцию пользовательского преобразования с тем же вымышленным списком шаблонных параметров, что и operator ( ) .

void f1(int (*)(int)) {}
void f2(char (*)(int)) {}
void h(int (*)(int)) {}  // #1
void h(char (*)(int)) {} // #2
auto glambda = [](auto a) { return a; };
f1(glambda); // OK
f2(glambda); // error: not convertible
h(glambda);  // OK: calls #1 since #2 is not convertible
int& (*fpi)(int*) = [](auto* a) -> auto& { return *a; }; // OK
(начиная с C++14)


Значение, возвращаемое функцией преобразования, является указателем на функцию с C++ language linkage , которая при вызове имеет тот же эффект, что и вызов оператора вызова функции типа замыкания на экземпляре типа замыкания, сконструированном по умолчанию.

(до C++14)

Значение, возвращаемое функцией преобразования (шаблоном), является указателем на функцию с C++ language linkage , которая при вызове имеет тот же эффект, что и:

  • для необобщенных лямбда-выражений - вызов operator ( ) типа замыкания на экземпляре типа замыкания, сконструированном по умолчанию.
  • для обобщенных лямбда-выражений - вызов соответствующей специализации operator ( ) обобщенного лямбда-выражения на экземпляре типа замыкания, сконструированном по умолчанию.
(начиная с C++14)
(до C++23)

Значение, возвращаемое функцией преобразования (шаблоном), является:

  • если operator ( ) является статическим - указателем на этот operator ( ) с C++ language linkage ,
  • иначе - указателем на функцию с C++ language linkage , которая при вызове имеет тот же эффект, что и:
    • для необобщенных лямбда-выражений - вызов operator ( ) типа замыкания на экземпляре типа замыкания, сконструированном по умолчанию.
    • для обобщенных лямбда-выражений - вызов соответствующей специализации operator ( ) обобщенного лямбда-выражения на экземпляре типа замыкания, сконструированном по умолчанию.
(начиная с C++23)


Эта функция является constexpr, если оператор вызова функции (или специализация, для обобщенных лямбд) является constexpr.

auto Fwd = [](int(*fp)(int), auto a) { return fp(a); };
auto C = [](auto a) { return a; };
static_assert(Fwd(C, 3) == 3);  // OK
auto NC = [](auto a) { static int s; return a; };
static_assert(Fwd(NC, 3) == 3); // error: no specialization can be
                                // constexpr because of static s

Если оператор замыкающего объекта operator ( ) имеет спецификацию исключения без генерации исключений, то указатель, возвращаемый этой функцией, имеет тип указателя на noexcept-функцию.

(начиная с C++17)

ClosureType:: ClosureType()

ClosureType ( ) = default ;
(начиная с C++20)
(только если не указаны захваты)
ClosureType ( const ClosureType & ) = default ;
ClosureType ( ClosureType && ) = default ;

Типы замыканий не являются DefaultConstructible . Типы замыканий не имеют конструктора по умолчанию.

(до C++20)

Если не указаны захваты , тип замыкания имеет конструктор по умолчанию, заданный как default. В противном случае он не имеет конструктора по умолчанию (это включает случай, когда есть захват по умолчанию , даже если он фактически ничего не захватывает).

(начиная с C++20)

Конструктор копирования и конструктор перемещения объявлены как defaulted и могут быть неявно определены в соответствии с обычными правилами для конструкторов копирования и конструкторов перемещения .

ClosureType:: operator=(const ClosureType&)

ClosureType & operator = ( const ClosureType & ) = delete ;
(до C++20)
ClosureType & operator = ( const ClosureType & ) = default ;
ClosureType & operator = ( ClosureType && ) = default ;
(начиная с C++20)
(только если не указаны захваты)
ClosureType & operator = ( const ClosureType & ) = delete ;
(начиная с C++20)
(в противном случае)

Оператор копирующего присваивания определен как удаленный (а оператор перемещающего присваивания не объявлен). Типы замыканий не являются CopyAssignable .

(до C++20)

Если не указаны захваты , тип замыкания имеет оператор копирующего присваивания по умолчанию и оператор перемещающего присваивания по умолчанию. В противном случае он имеет удаленный оператор копирующего присваивания (это включает случай, когда есть захват по умолчанию , даже если он фактически ничего не захватывает).

(начиная с C++20)

ClosureType:: ~ClosureType()

~ClosureType ( ) = default ;

Деструктор объявлен неявно.

ClosureType:: Captures

T1 a ;

T2 b ;

...

Если лямбда-выражение захватывает что-либо по копии (либо неявно с помощью спецификатора захвата [=] , либо явно с захватом, который не включает символ &, например [a, b, c] ), то тип замыкания включает неименованные нестатические члены данных, объявленные в неуказанном порядке, которые содержат копии всех сущностей, захваченных таким образом.

Те члены данных, которые соответствуют захватам без инициализаторов, прямо инициализируются при вычислении лямбда-выражения. Те, которые соответствуют захватам с инициализаторами, инициализируются в соответствии с требованиями инициализатора (может быть копирующая или прямая инициализация). Если захватывается массив, элементы массива прямо инициализируются в порядке возрастания индекса. Порядок инициализации членов данных соответствует порядку их объявления (который не указан).

Тип каждого члена данных соответствует типу соответствующей захваченной сущности, за исключением случая, когда сущность имеет ссылочный тип (в этом случае ссылки на функции захватываются как lvalue-ссылки на указанные функции, а ссылки на объекты захватываются как копии указанных объектов).

Для сущностей, которые захватываются по ссылке (с помощью capture-default [&] или при использовании символа &, например [&a, &b, &c] ), не указано, объявляются ли дополнительные члены данных в типе замыкания , но любые такие дополнительные члены должны удовлетворять требованиям LiteralType (начиная с C++17) .


Лямбда-выражения не разрешены в невычисляемых выражениях , аргументах шаблонов , объявлениях псевдонимов , объявлениях typedef , а также где бы то ни было в объявлении функции (или шаблона функции) за исключением тела функции и аргументов по умолчанию функции.

(до C++20)

Захват лямбда-выражения

Захваты определяют внешние переменные, доступные внутри тела лямбда-функции. Их синтаксис определяется следующим образом:

capture-default (1)
capture-list (2)
capture-default , capture-list (3)
capture-default - один из & и =
capture-list - список, разделённый запятыми, из capture ов


Синтаксис capture определяется следующим образом:

identifier (1)
identifier ... (2)
identifier initializer (3) (начиная с C++14)
& identifier (4)
& identifier ... (5)
& identifier initializer (6) (начиная с C++14)
this (7)
* this (8) (начиная с C++17)
... identifier initializer (9) (начиная с C++20)
& ... identifier initializer (10) (начиная с C++20)
1) простая захват по значению
2) простая захват по копированию, которая является pack expansion
3) захват по копии с инициализатором
4) простая захват по ссылке
5) простая захват по ссылке, которая является pack expansion
6) захват по ссылке с инициализатором
7) простая захват по ссылке текущего объекта
8) простая захват по копированию текущего объекта
9) захват по копии с инициализатором, который является раскрытием пакета
10) захват по ссылке с инициализатором, который является развертыванием пакета

Если capture-default является & , последующие простые захваты не должны начинаться с & .

struct S2 { void f(int i); };
void S2::f(int i)
{
    [&] {};          // OK: захват по ссылке по умолчанию
    [&, i] {};       // OK: захват по ссылке, кроме i, который захватывается по копии
    [&, &i] {};      // Ошибка: захват по ссылке, когда захват по ссылке установлен по умолчанию
    [&, this] {};    // OK, эквивалентно [&]
    [&, this, i] {}; // OK, эквивалентно [&, i]
}

Если capture-default имеет значение = , последующие простые захваты должны начинаться с & или быть *this (начиная с C++17) или this (начиная с C++20) .

struct S2 { void f(int i); };
void S2::f(int i)
{
    [=] {};        // OK: захват по умолчанию по копии
    [=, &i] {};    // OK: захват по копии, за исключением i, который захватывается по ссылке
    [=, *this] {}; // до C++17: Ошибка: неверный синтаксис
                   // начиная с C++17: OK: захватывает включающий объект S2 по копии
    [=, this] {};  // до C++20: Ошибка: this при = по умолчанию
                   // начиная с C++20: OK, то же самое что [=]
}

Любой захват может появляться только один раз, и его имя должно отличаться от любого имени параметра:

struct S2 { void f(int i); };
void S2::f(int i)
{
    [i, i] {};        // Ошибка: повторение i
    [this, *this] {}; // Ошибка: повторение "this" (C++17)
    [i] (int i) {};   // Ошибка: параметр и захват имеют одинаковое имя
}

Лямбда-выражение может использовать переменную без её захвата, если переменная

Лямбда-выражение может читать значение переменной без её захвата, если переменная

  • имеет константный не-volatile целочисленный или перечисляемый тип и была инициализирована с помощью константного выражения , или
  • является constexpr и не имеет изменяемых членов.

Текущий объект ( * this ) может быть неявно захвачен, если присутствует стандартный захват. Если он захвачен неявно, он всегда захватывается по ссылке, даже если стандартный захват имеет значение = . Неявный захват * this при стандартном захвате = устарел. (начиная с C++20)

Только лямбда-выражения, удовлетворяющие любому из следующих условий, могут иметь capture-default или capture без инициализаторов:

(начиная с C++26)

Для такого лямбда-выражения область достижимости определяется как набор охватывающих областей видимости вплоть до и включая самую внутреннюю охватывающую функцию (и её параметры). Это включает вложенные области видимости блоков и области видимости охватывающих лямбда-выражений, если данная лямбда является вложенной.

Идентификатор в любой capture-записи без инициализатора (кроме capture-записи this ) ищется с использованием обычного поиска имени без квалификации в охватывающей области видимости лямбда-выражения. Результатом поиска должна быть переменная с автоматической продолжительностью хранения, объявленная в охватывающей области видимости , либо структурированная привязка , соответствующая переменная которой удовлетворяет указанным требованиям (начиная с C++20) . Сущность является явно захваченной .

Захват с инициализатором, называемый init-capture , действует так, как если бы он объявлял и явно захватывал переменную, объявленную со спецификатором типа auto и тем же инициализатором, чья область объявления является телом лямбда-выражения (то есть она не находится в области видимости в своем инициализаторе), за исключением того, что:

  • если захват осуществляется по значению, введенный нестатический элемент данных объекта замыкания является другим способом ссылки на эту переменную;
    • другими словами, исходная переменная фактически не существует, а вывод типа через auto и инициализация применяются к нестатическому элементу данных;
  • если захват осуществляется по ссылке, время жизни ссылочной переменной заканчивается, когда заканчивается время жизни объекта замыкания.

Это используется для захвата типов, допускающих только перемещение, с помощью захвата, такого как x = std :: move ( x ) .

Это также делает возможным захват по константной ссылке с помощью & cr = std:: as_const ( x ) или аналогичного.

int x = 4;
auto y = [&r = x, x = x + 1]() -> int
{
    r += 2;
    return x * x;
}(); // updates ::x to 6 and initializes y to 25.
(since C++14)

Если captures имеет capture-default и не захватывает явно объемлющий объект (как this или * this ), или автоматическую переменную, которая является odr-usable в теле лямбды , или structured binding , соответствующая переменная которой имеет атомарную длительность хранения (since C++20) , она захватывает сущность неявно , если сущность упоминается в potentially-evaluated выражении внутри выражения (включая случаи, когда неявный this - > добавляется перед использованием нестатического члена класса).

Для определения неявных захватов, typeid никогда не считается операцией, делающей свои операнды невычисляемыми.

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

(since C++17)
void f(int, const int (&)[2] = {}) {}   // #1
void f(const int&, const int (&)[1]) {} // #2
struct NoncopyableLiteralType
{
    constexpr explicit NoncopyableLiteralType(int n) : n_(n) {}
    NoncopyableLiteralType(const NoncopyableLiteralType&) = delete;
    int n_;
};
void test()
{
    const int x = 17;
    auto l0 = []{ f(x); };           // OK: вызывает #1, не захватывает x
    auto g0 = [](auto a) { f(x); };  // то же самое, что выше
    auto l1 = [=]{ f(x); };          // OK: захватывает x (начиная с P0588R1) и вызывает #1
                                     // захват может быть оптимизирован
    auto g1 = [=](auto a) { f(x); }; // то же самое, что выше
    auto ltid = [=]{ typeid(x); };   // OK: захватывает x (начиная с P0588R1)
                                     // даже несмотря на то, что x не вычисляется
                                     // захват может быть оптимизирован
    auto g2 = [=](auto a)
    {
        int selector[sizeof(a) == 1 ? 1 : 2] = {};
        f(x, selector); // OK: является зависимым выражением, поэтому захватывает x
    };
    auto g3 = [=](auto a)
    {
        typeid(a + x);  // захватывает x независимо от того,
                        // является ли a + x невычисляемым операндом
    };
    constexpr NoncopyableLiteralType w{42};
    auto l4 = []{ return w.n_; };      // OK: w не используется как odr-выражение, захват не требуется
    // auto l5 = [=]{ return w.n_; };  // ошибка: w должен быть захвачен по копии
}

Если тело лямбда-выражения odr-использует сущность, захваченную по значению, происходит обращение к члену типа замыкания. Если сущность не odr-используется, обращение происходит к исходному объекту:

void f(const int*);
void g()
{
    const int N = 10;
    [=]
    { 
        int arr[N]; // не является odr-использованием: ссылается на const int N функции g
        f(&N); // odr-использование: приводит к захвату N (по копии)
               // &N является адресом члена N объекта замыкания, а не N функции g
    }();
}

Если лямбда ODR-использует ссылку, захваченную по ссылке, она использует объект, на который ссылается исходная ссылка, а не саму захваченную ссылку:

#include <iostream>
auto make_function(int& x)
{
    return [&] { std::cout << x << '\n'; };
}
int main()
{
    int i = 3;
    auto f = make_function(i); // использование x в f напрямую связывается с i
    i = 5;
    f(); // OK: выводит 5
}

В теле лямбды с захватом по умолчанию = , тип любой захватываемой сущности трактуется так, как если бы она была захвачена (и поэтому часто добавляется const-квалификация, если лямбда не mutable ), даже если сущность находится в невычисляемом операнде и не захватывается (например, в decltype ):

void f3()
{
    float x, &r = x;
    [=]
    { // x и r не захватываются (появление в операнде decltype не является odr-использованием)
        decltype(x) y1;        // y1 имеет тип float
        decltype((x)) y2 = y1; // y2 имеет тип float const&, потому что эта лямбда
                               // не является mutable, а x - lvalue
        decltype(r) r1 = y1;   // r1 имеет тип float& (преобразование не рассматривается)
        decltype((r)) r2 = y2; // r2 имеет тип float const&
    };
}

Любая сущность, захваченная лямбдой (неявно или явно), является odr-используемой лямбда-выражением (следовательно, неявный захват вложенной лямбдой вызывает неявный захват в охватывающей лямбде).

Все неявно захваченные переменные должны быть объявлены в пределах достижимой области видимости лямбда-выражения.

Если лямбда захватывает объемлющий объект (как this или * this ), то либо ближайшая объемлющая функция должна быть нестатической функцией-членом, либо лямбда должна находиться в инициализаторе элемента по умолчанию :

struct s2
{
    double ohseven = .007;
    auto f() // ближайшая охватывающая функция для следующих двух лямбда-выражений
    {
        return [this]      // захват охватывающего s2 по ссылке
        {
            return [*this] // захват охватывающего s2 по копии (C++17)
            {
                return ohseven;// OK
            }
        }();
    }
    auto g()
    {
        return [] // ничего не захватывает
        { 
            return [*this] {};// ошибка: *this не захвачен внешним лямбда-выражением
        }();
    }
};

Если лямбда-выражение (или специализация оператора вызова функции обобщённой лямбды) (since C++14) ODR-использует * this или любую переменную с автоматической продолжительностью хранения, она должна быть захвачена лямбда-выражением.

void f1(int i)
{
    int const N = 20;
    auto m1 = [=]
    {
        int const M = 30;
        auto m2 = [i]
        {
            int x[N][M]; // N и M не используются как odr-использование
                         // (нормально, что они не захвачены)
            x[0][0] = i; // i явно захвачен m2
                         // и неявно захвачен m1
        };
    };
    struct s1 // локальный класс внутри f1()
    {
        int f;
        void work(int n) // нестатическая функция-член
        {
            int m = n * n;
            int j = 40;
            auto m3 = [this, m]
            {
                auto m4 = [&, j] // ошибка: j не захвачен m3
                {
                    int x = n; // ошибка: n неявно захвачен m4
                               // но не захвачен m3
                    x += m;    // OK: m неявно захвачен m4
                               // и явно захвачен m3
                    x += i;    // ошибка: i находится вне области видимости
                               // (которая заканчивается на work())
                    x += f;    // OK: this неявно захвачен m4
                               // и явно захвачен m3
                };
            };
        }
    };
}

Члены класса не могут быть захвачены явно в захвате без инициализатора (как упомянуто выше, только переменные разрешены в capture-list ):

class S
{
    int x = 0;
    void f()
    {
        int i = 0;
    //  auto l1 = [i, x] { use(i, x); };      // ошибка: x не является переменной
        auto l2 = [i, x = x] { use(i, x); };  // OK, захват по копии
        i = 1; x = 1; l2(); // вызывает use(0,0)
        auto l3 = [i, &x = x] { use(i, x); }; // OK, захват по ссылке
        i = 2; x = 2; l3(); // вызывает use(1,2)
    }
};

Когда лямбда захватывает член с помощью неявного захвата по копии, она не создаёт копию этой переменной-члена: использование переменной-члена m трактуется как выражение ( * this ) . m , и * this всегда неявно захватывается по ссылке:

class S
{
    int x = 0;
    void f()
    {
        int i = 0;
        auto l1 = [=] { use(i, x); }; // захватывает копию i и
                                      // копию указателя this
        i = 1; x = 1; l1();           // вызывает use(0, 1), как если бы
                                      // i по копии и x по ссылке
        auto l2 = [i, this] { use(i, x); }; // то же самое, что выше, явно указано
        i = 2; x = 2; l2();           // вызывает use(1, 2), как если бы
                                      // i по копии и x по ссылке
        auto l3 = [&] { use(i, x); }; // захватывает i по ссылке и
                                      // копию указателя this
        i = 3; x = 2; l3();           // вызывает use(3, 2), как если бы
                                      // i и x оба по ссылке
        auto l4 = [i, *this] { use(i, x); }; // создает копию *this,
                                             // включая копию x
        i = 4; x = 4; l4();           // вызывает use(3, 2), как если бы
                                      // i и x оба по копии
    }
};

Если лямбда-выражение появляется в аргументе по умолчанию , оно не может явно или неявно захватывать что-либо , если только все захваты не имеют инициализаторов, удовлетворяющих ограничениям выражения, появляющегося в аргументе по умолчанию (начиная с C++14) :

void f2()
{
    int i = 1;
    void g1( int = [i] { return i; }() ); // ошибка: захватывает что-то
    void g2( int = [i] { return 0; }() ); // ошибка: захватывает что-то
    void g3( int = [=] { return i; }() ); // ошибка: захватывает что-то
    void g4( int = [=] { return 0; }() );       // OK: без захвата
    void g5( int = [] { return sizeof i; }() ); // OK: без захвата
    // C++14
    void g6( int = [x = 1] { return x; }() ); // OK: 1 может присутствовать
                                              //     в аргументе по умолчанию
    void g7( int = [x = i] { return x; }() ); // ошибка: i не может присутствовать
                                              //        в аргументе по умолчанию
}

Члены анонимных объединений не могут быть захвачены. Битовые поля могут быть захвачены только по копии.

Если вложенная лямбда m2 захватывает нечто, что также захватывается непосредственно охватывающей лямбдой m1 , то захват m2 преобразуется следующим образом:

  • если внешняя лямбда m1 захватывает по значению, m2 захватывает нестатический член замыкающего типа m1 , а не исходную переменную или * this ; если m1 не является mutable, нестатический член данных считается константно-квалифицированным.
  • если внешняя лямбда m1 захватывает по ссылке, m2 захватывает исходную переменную или * this .
#include <iostream>
int main()
{
    int a = 1, b = 1, c = 1;
    auto m1 = [a, &b, &c]() mutable
    {
        auto m2 = [a, b, &c]() mutable
        {
            std::cout << a << b << c << '\n';
            a = 4; b = 4; c = 4;
        };
        a = 3; b = 3; c = 3;
        m2();
    };
    a = 2; b = 2; c = 2;
    m1();                             // вызывает m2() и выводит 123
    std::cout << a << b << c << '\n'; // выводит 234
}

Если лямбда-выражение захватывает какие-либо переменные, тип явного параметра объекта (если он есть) оператора вызова функции может быть только

  • типом замыкания,
  • типом класса, открыто и однозначно производного от типа замыкания, или
  • ссылкой на возможно cv-квалифицированный такой тип.
struct C 
{
    template<typename T>
    C(T);
};
void func(int i) 
{
    int x = [=](this auto&&) { return i; }();  // OK
    int y = [=](this C) { return i; }();       // error
    int z = [](this C) { return 42; }();       // OK
    auto lambda = [n = 42] (this auto self) { return n; };
    using Closure = decltype(lambda);
    struct D : private Closure {
        D(Closure l) : Closure(l) {}
        using Closure::operator();
        friend Closure;
    };
    D{lambda}(); // error
}
(начиная с C++23)

Примечания

Макрос тестирования возможностей Значение Стандарт Возможность
__cpp_lambdas 200907L (C++11) Лямбда-выражения
__cpp_generic_lambdas 201304L (C++14) Обобщённые лямбда-выражения
201707L (C++20) Явный список параметров шаблона для обобщённых лямбд
__cpp_init_captures 201304L (C++14) Инициализирующий захват в лямбдах
201803L (C++20) Разрешение расширения пакетов в инициализирующем захвате лямбд
__cpp_capture_star_this 201603L (C++17) Захват * this по значению как [ = , * this ]
__cpp_constexpr 201603L (C++17) constexpr лямбда
__cpp_static_call_operator 202207L (C++23) static operator ( ) для лямбд без захвата

Правило для неявного захвата в лямбда-выражениях было немного изменено в соответствии с отчётом о дефекте P0588R1 . По состоянию на октябрь 2023 года некоторые основные реализации ещё не полностью внедрили этот отчёт, поэтому старое правило, которое определяет odr-использование , всё ещё применяется в некоторых случаях.

Старое правило до P0588R1

Если captures содержит capture-default и не захватывает явно объемлющий объект (как this или *this ), или автоматическую переменную, которая является odr-usable в теле лямбды , или structured binding , соответствующая переменная которого имеет атомарную длительность хранения (since C++20) , она захватывает сущность неявно , если сущность

  • упоминается в potentially-evaluated выражении внутри выражения, которое зависит от параметра шаблона generic lambda, или
(since C++14)

Пример

Этот пример показывает, как передать лямбду в обобщенный алгоритм и как объекты, полученные из лямбда-выражения, могут быть сохранены в std::function объектах.

#include <algorithm>
#include <functional>
#include <iostream>
#include <vector>
int main()
{
    std::vector<int> c{1, 2, 3, 4, 5, 6, 7};
    int x = 5;
    c.erase(std::remove_if(c.begin(), c.end(), [x](int n) { return n < x; }), c.end());
    std::cout << "c: ";
    std::for_each(c.begin(), c.end(), [](int i) { std::cout << i << ' '; });
    std::cout << '\n';
    // тип замыкания не может быть назван, но может быть выведен с помощью auto
    // начиная с C++14, лямбда может иметь аргументы по умолчанию
    auto func1 = [](int i = 6) { return i + 4; };
    std::cout << "func1: " << func1() << '\n';
    // как и все вызываемые объекты, замыкания могут быть захвачены в std::function
    // (это может привести к излишним накладным расходам)
    std::function<int(int)> func2 = [](int i) { return i + 4; };
    std::cout << "func2: " << func2(6) << '\n';
    constexpr int fib_max {8};
    std::cout << "Эмулировать вызовы `рекурсивных лямбда`:\nЧисла Фибоначчи: ";
    auto nth_fibonacci = [](int n)
    {
        std::function<int(int, int, int)> fib = [&](int n, int a, int b)
        {
            return n ? fib(n - 1, a + b, a) : b;
        };
        return fib(n, 0, 1);
    };
    for (int i{1}; i <= fib_max; ++i)
        std::cout << nth_fibonacci(i) << (i < fib_max ? ", " : "\n");
    std::cout << "Альтернативный подход к рекурсии лямбда-функций:\nЧисла Фибоначчи: ";
    auto nth_fibonacci2 = [](auto self, int n, int a = 0, int b = 1) -> int
    {
        return n ? self(self, n - 1, a + b, a) : b;
    };
    for (int i{1}; i <= fib_max; ++i)
        std::cout << nth_fibonacci2(nth_fibonacci2, i) << (i < fib_max ? ", " : "\n");
#ifdef __cpp_explicit_this_parameter
    std::cout << "Подход C++23 к рекурсии лямбда-функций:\n";
    auto nth_fibonacci3 = [](this auto self, int n, int a = 0, int b = 1) -> int
    {
         return n ? self(n - 1, a + b, a) : b;
    };
    for (int i{1}; i <= fib_max; ++i)
        std::cout << nth_fibonacci3(i) << (i < fib_max ? ", " : "\n");
#endif
}

Возможный вывод:

c: 5 6 7
func1: 10
func2: 10
Эмуляция вызовов `рекурсивных лямбда`:
Числа Фибоначчи: 0, 1, 1, 2, 3, 5, 8, 13
Альтернативный подход к рекурсии лямбда-функций:
Числа Фибоначчи: 0, 1, 1, 2, 3, 5, 8, 13

Отчёты о дефектах

Следующие отчеты об изменениях в поведении, содержащие описания дефектов, были применены ретроактивно к ранее опубликованным стандартам C++.

DR Применяется к Поведение в опубликованной версии Корректное поведение
CWG 974 C++11 аргументы по умолчанию не допускались в
списке параметров лямбда-выражения
разрешено
CWG 1048
( N3638 )
C++11 возвращаемый тип мог выводиться только для лямбда-
выражений, содержащих только один return оператор
улучшено выведение
возвращаемого типа
CWG 1249 C++11 неясно, считается ли захваченный член
объемлющей не-mutable лямбды const или нет
считается const
CWG 1557 C++11 языковая связь возвращаемого типа функции
функции преобразования типа замыкания не была указана
имеет языковую связь C++
CWG 1607 C++11 лямбда-выражения могли появляться в
сигнатурах функций и шаблонов функций
не разрешено
CWG 1612 C++11 члены анонимных объединений могли быть захвачены не разрешено
CWG 1722 C++11 функция преобразования для лямбд без захвата
имела неуказанную спецификацию исключений
функция преобразования
является noexcept
CWG 1772 C++11 семантика __func__ в теле лямбды была неясна ссылается на operator()
класса замыкания
CWG 1780 C++14 было неясно, могут ли члены типов замыканий обобщенных
лямбд быть явно инстанцированы или явно специализированы
ни то, ни другое не разрешено
CWG 1891 C++11 замыкание имело удаленный конструктор по умолчанию
и неявные конструкторы копирования/перемещения
нет конструктора по умолчанию и
конструкторы копирования/перемещения по умолчанию
CWG 1937 C++11 относительно эффекта вызова результата
функции преобразования, не было указано, на каком
объекте вызов его operator ( ) имеет тот же эффект
на созданном по умолчанию
экземпляре типа замыкания
CWG 1973 C++11 список параметров operator ( ) типа замыкания
мог ссылаться на список параметров, заданный в trailing
может ссылаться только
на params
CWG 2011 C++11 для ссылки, захваченной по ссылке, не было указано,
на какую сущность ссылается идентификатор захвата
ссылается на исходную
сущность
CWG 2095 C++11 поведение захвата rvalue-ссылок
на функции по копии было неясно
прояснено
CWG 2211 C++11 поведение не было указано, если захват
имеет то же имя, что и параметр
программа является
некорректной в этом случае
CWG 2358 C++14 лямбда-выражения, появляющиеся в аргументах по умолчанию,
должны были быть без захвата, даже если все захваты
инициализируются выражениями, которые могут появляться в аргументах по умолчанию
разрешить такие лямбда-
выражения с захватами
CWG 2509 C++17 каждый спецификатор мог иметь несколько
вхождений в последовательности спецификаторов
каждый спецификатор может
появляться только один раз в
последовательности спецификаторов
CWG 2561 C++23 лямбда с явным параметром объекта могла иметь
функцию преобразования в нежелательный тип указателя на функцию
не имеет такой
функции преобразования
CWG 2881 C++23 operator ( ) с явным параметром мог инстанцироваться для
производного класса, когда наследование не было публичным или было неоднозначным
сделано некорректным
P0588R1 C++11 правило для неявного захвата лямбды обнаруживало odr-использование обнаружение упрощено

Смотрите также

auto спецификатор (C++11) указывает тип, выводимый из выражения
(C++11)
копируемая обёртка для любого копируемого вызываемого объекта
(шаблон класса)
перемещаемая обёртка для любого вызываемого объекта, поддерживающего квалификаторы в заданной сигнатуре вызова
(шаблон класса)

Внешние ссылки

Вложенная функция - функция, которая определена внутри другой ( охватывающей ) функции.