Spec-Zone.ru › C++

Lambda-выражения (с C++11)

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

Синтаксис

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

Объяснение

captures - список захватов, разделённых запятыми, нулевой или более, необязательно начинающийся со capture-default.

Подробное описание захватов см. ниже.

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

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

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

  • имеет тип const non-volatile integral или enumeration и была инициализирована константным выражением, или
  • является constexpr и не имеет мутабельных членов.
tparams - список параметров шаблона, разделённых запятыми, непустой, используемый для присвоения имён параметрам шаблона в лямбда-выражении общего вида (см. ClosureType::operator() ниже)
t-requires - добавляет ограничения к tparams

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

(с C++23)
front-attr - (с C++23) последовательность спецификаторов атрибутов применяется к operator() типа замыкания (и, следовательно, можно использовать атрибут [[noreturn]])
params - список параметров operator() типа замыкания
specs - Список следующих спецификаторов, каждый спецификатор разрешён не более одного раза в каждой последовательности. Если не указано, объекты, захваченные по копированию, являются const в теле лямбда-выражения.
  • mutable: позволяет body изменять объекты, захваченные по копированию, и вызывать их неконстантные методы членов
  • constexpr: явно указывает, что operator() является constexpr функцией. Если этот спецификатор отсутствует, operator() будет constexpr, если он удовлетворяет всем требованиям constexpr функций
(с C++17)
  • consteval: указывает, что operator() является немедленной функцией. consteval и constexpr нельзя использовать одновременно.
(с C++20)
  • static: указывает, что operator() является статическим членом-функцией. mutable и static нельзя использовать одновременно, и captures должны быть пустыми, если присутствует static.
(с C++23)
exception - предоставляет динамическое спецификацию исключений или(до C++20) спецификатор noexcept для operator() типа замыкания
back-attr - последовательность спецификаторов атрибутов применяется к типу operator() типа замыкания (и, следовательно, нельзя использовать атрибут [[noreturn]])
trailing-type - -> ret, где ret указывает тип возвращаемого значения
requires - (с C++20) добавляет ограничения к operator() типа замыкания
body - тело функции

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

(с C++14)

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

Выражение лямбда является выражением prvalue уникального безымянного не-union не-агрегатного не-структурного типа класса, известного как тип замыкания, который объявляется (для целей ADL) в самом маленьком блоке области видимости, области видимости класса или области видимости пространства имён, содержащем выражение лямбда. Тип замыкания имеет следующие члены, их нельзя явно инициализировать, явно специализировать, или(с C++14) назвать в friend-объявлении:

ClosureType::operator()(params)

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

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

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

Тип возвращаемого значения operator() — это тип, указанный в trailing-type.

Если trailing-type не указан, тип возвращаемого значения operator() определяется следующим образом:

  • Если body состоит только из оператора return, возвращающего выражение, тип возвращаемого значения — это тип этого выражения после преобразования lvalue-to-rvalue, преобразования массива в указатель и преобразования функции в указатель.
  • В противном случае тип возвращаемого значения — void.
(до C++14)

Если trailing-type не указан или указан как -> auto, тип возвращаемого значения operator() автоматически выводится.

(с C++14)

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

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

(с C++17)

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

(с C++20)

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

(с C++23)

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

// 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
(с 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)

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

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

struct X
{
    int x, y;
    int operator()(int);
    void f()
    {
        // the context of the following lambda is the member function X::f
        [=]() -> int
        {
            return operator()(this->x + y); // X::operator()(this->x + (*this).y)
                                            // this has type X*
        };
    }
};

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

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

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

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 = /* see below */;
template<template-params>
operator fptr_t<template-params>() const noexcept;
(с C++14)
(до C++17)
template<template-params> using fptr_t = /* see below */;
template<template-params>
constexpr operator fptr_t<template-params>() const noexcept;
(с C++17)

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

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

(с 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++ языковой связью, которая при вызове имеет тот же эффект, что и вызов оператора вызова функции типа закрытия на экземпляре типа закрытия, созданном по умолчанию.

(до C++14)

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

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

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

  • если operator() является статической, указатель на эту operator() с C++ языковой связью,
  • в противном случае, указатель на функцию с C++ языковой связью, которая при вызове имеет тот же эффект, что и:
    • для необобщённых лямбда-выражений, вызов оператора вызова функции типа закрытия на экземпляре типа закрытия, созданном по умолчанию.
    • для обобщённых лямбда-выражений, вызов соответствующей специализации обобщённого лямбда-выражения на экземпляре типа закрытия, созданном по умолчанию.
(с 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)

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

(с C++20)

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

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::Захваты

T1 a;
T2 b;
...

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

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

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

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

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

(до C++20)

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

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

  • & (неявно захватывает используемые переменные со сроком существования автоматического хранения по ссылке) и
  • = (неявно захватывает используемые переменные со сроком существования автоматического хранения по значению).

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

Синтаксис отдельного захвата в списке захватов:

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

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

struct S2 { void f(int i); };
void S2::f(int i)
{
    [&] {};          // OK: by-reference capture default
    [&, i] {};       // OK: by-reference capture, except i is captured by copy
    [&, &i] {};      // Error: by-reference capture when by-reference is the default
    [&, this] {};    // OK, equivalent to [&]
    [&, this, i] {}; // OK, equivalent to [&, i]
}

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

struct S2 { void f(int i); };
void S2::f(int i)
{
    [=] {};        // OK: by-copy capture default
    [=, &i] {};    // OK: by-copy capture, except i is captured by reference
    [=, *this] {}; // until C++17: Error: invalid syntax
                   // since C++17: OK: captures the enclosing S2 by copy
    [=, this] {};  // until C++20: Error: this when = is the default
                   // since C++20: OK, same as [=]
}

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

struct S2 { void f(int i); };
void S2::f(int i)
{
    [i, i] {};        // Error: i repeated
    [this, *this] {}; // Error: "this" repeated (C++17)
 
    [i] (int i) {};   // Error: parameter and capture have the same name
}
END_OF_DOCUMENT_MARKER

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

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

Захват с инициализатором ведет себя так, как будто он объявляет и явно захватывает переменную, объявленную с указанием типа 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.
(с C++14)

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

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

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

(с 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: calls #1, does not capture x
    auto g0 = [](auto a) { f(x); };  // same as above
 
    auto l1 = [=]{ f(x); };          // OK: captures x (since P0588R1) and calls #1
                                     // the capture can be optimized away
    auto g1 = [=](auto a) { f(x); }; // same as above
 
    auto ltid = [=]{ typeid(x); };   // OK: captures x (since P0588R1)
                                     // even though x is unevaluated
                                     // the capture can be optimized away
 
    auto g2 = [=](auto a)
    {
        int selector[sizeof(a) == 1 ? 1 : 2] = {};
        f(x, selector); // OK: is a dependent expression, so captures x
    };
 
    auto g3 = [=](auto a)
    {
        typeid(a + x);  // captures x regardless of
                        // whether a + x is an unevaluated operand
    };
 
    constexpr NoncopyableLiteralType w{42};
    auto l4 = []{ return w.n_; };      // OK: w is not odr-used, capture is unnecessary
    // auto l5 = [=]{ return w.n_; };  // error: w needs to be captured by copy
}

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

void f(const int*);
void g()
{
    const int N = 10;
    [=]
    { 
        int arr[N]; // not an odr-use: refers to g's const int N
        f(&N); // odr-use: causes N to be captured (by copy)
               // &N is the address of the closure object's member N, not g's N
    }();
}

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

#include <iostream>
 
auto make_function(int& x)
{
    return [&] { std::cout << x << '\n'; };
}
 
int main()
{
    int i = 3;
    auto f = make_function(i); // the use of x in f binds directly to i
    i = 5;
    f(); // OK: prints 5
}

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

void f3()
{
    float x, &r = x;
    [=]
    { // x and r are not captured (appearance in a decltype operand is not an odr-use)
        decltype(x) y1;        // y1 has type float
        decltype((x)) y2 = y1; // y2 has type float const& because this lambda
                               // is not mutable and x is an lvalue
        decltype(r) r1 = y1;   // r1 has type float& (transformation not considered)
        decltype((r)) r2 = y2; // r2 has type float const&
    };
}

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

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

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

struct s2
{
    double ohseven = .007;
    auto f() // nearest enclosing function for the following two lambdas
    {
        return [this]      // capture the enclosing s2 by reference
        {
            return [*this] // capture the enclosing s2 by copy (C++17)
            {
                return ohseven; // OK
            }
        }();
    }
 
    auto g()
    {
        return [] // capture nothing
        { 
            return [*this] {}; // error: *this not captured by outer lambda-expression
        }();
    }
};

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

void f1(int i)
{
    int const N = 20;
    auto m1 = [=]
    {
        int const M = 30;
        auto m2 = [i]
        {
            int x[N][M]; // N and M are not odr-used 
                         // (ok that they are not captured)
            x[0][0] = i; // i is explicitly captured by m2
                         // and implicitly captured by m1
        };
    };
 
    struct s1 // local class within f1()
    {
        int f;
        void work(int n) // non-static member function
        {
            int m = n * n;
            int j = 40;
            auto m3 = [this, m]
            {
                auto m4 = [&, j] // error: j is not captured by m3
                {
                    int x = n; // error: n is implicitly captured by m4
                               // but not captured by m3
                    x += m;    // OK: m is implicitly captured by m4
                               // and explicitly captured by m3
                    x += i;    // error: i is outside of the reaching scope
                               // (which ends at work())
                    x += f;    // OK: this is captured implicitly by m4
                               // and explicitly captured by m3
                };
            };
        }
    };
}

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

class S
{
    int x = 0;
 
    void f()
    {
        int i = 0;
    //  auto l1 = [i, x] { use(i, x); };      // error: x is not a variable
        auto l2 = [i, x = x] { use(i, x); };  // OK, copy capture
        i = 1; x = 1; l2(); // calls use(0,0)
        auto l3 = [i, &x = x] { use(i, x); }; // OK, reference capture
        i = 2; x = 2; l3(); // calls use(1,2)
    }
};

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

class S
{
    int x = 0;
 
    void f()
    {
        int i = 0;
 
        auto l1 = [=] { use(i, x); }; // captures a copy of i and
                                      // a copy of the this pointer
        i = 1; x = 1; l1();           // calls use(0, 1), as if
                                      // i by copy and x by reference
 
        auto l2 = [i, this] { use(i, x); }; // same as above, made explicit
        i = 2; x = 2; l2();           // calls use(1, 2), as if
                                      // i by copy and x by reference
 
        auto l3 = [&] { use(i, x); }; // captures i by reference and
                                      // a copy of the this pointer
        i = 3; x = 2; l3();           // calls use(3, 2), as if
                                      // i and x are both by reference
 
        auto l4 = [i, *this] { use(i, x); }; // makes a copy of *this,
                                             // including a copy of x
        i = 4; x = 4; l4();           // calls use(3, 2), as if
                                      // i and x are both by copy
    }
};

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

void f2()
{
    int i = 1;
 
    void g1(int = ([i] { return i; })()); // error: captures something
    void g2(int = ([i] { return 0; })()); // error: captures something
    void g3(int = ([=] { return i; })()); // error: captures something
 
    void g4(int = ([=] { return 0; })());       // OK: capture-less
    void g5(int = ([] { return sizeof i; })()); // OK: capture-less
 
    // C++14
    void g6(int = ([x = 1] { return x; }))(); // OK: 1 can appear
                                              //     in a default argument
    void g7(int = ([x = i] { return x; }))(); // error: i cannot appear
                                              //        in a default argument
}
(с C++11)

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

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

  • если окружающая лямбда m1 захватывает по копированию, m2 захватывает нестатическое поле типа замыкания m1, а не исходную переменную или *this; если m1 не мутабельная, нестатическое поле считается константным.
  • если окружающая лямбда 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();                             // calls m2() and prints 123
    std::cout << a << b << c << '\n'; // prints 234
}

Примечания

Макрос проверки функций Значение Стандарт Функция
__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) статический operator() для лямбда-выражений без захватов

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

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

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

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

Пример

Этот пример демонстрирует, как передать лямбда-выражение в общий алгоритм и как объекты, полученные из выражения лямбда, могут быть сохранены в 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';
 
    // the type of a closure cannot be named, but can be inferred with auto
    // since C++14, lambda could own default arguments
    auto func1 = [](int i = 6) { return i + 4; };
    std::cout << "func1: " << func1() << '\n';
 
    // like all callable objects, closures can be captured in std::function
    // (this may incur unnecessary overhead)
    std::function<int(int)> func2 = [](int i) { return i + 4; };
    std::cout << "func2: " << func2(6) << '\n';
 
    constexpr int fib_max {8};
    std::cout << "Emulate `recursive lambda` calls:\nFibonacci numbers: ";
    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 << "Alternative approach to lambda recursion:\nFibonacci numbers: ";
    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 approach to lambda recursion:\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
Emulate `recursive lambda` calls:
Fibonacci numbers: 0, 1, 1, 2, 3, 5, 8, 13
Alternative approach to lambda recursion:
Fibonacci numbers: 0, 1, 1, 2, 3, 5, 8, 13

Отчеты об ошибках

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

DR Применяется к Поведение, как опубликовано Корректное поведение
CWG 974 C++11 аргумент по умолчанию не разрешался в списке параметров выражения лямбда разрешено
CWG 1249 C++11 неясно, считается ли захваченная переменная окружения не-мутирующего лямбда-выражения 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__ в теле лямбда-выражения не была ясна она относится к оператору() класса закрытия
CWG 1780 C++14 не было ясно, могут ли члены типов закрытия генерических лямбда-выражений быть явно инстанцированы или явно специализированы ни то, ни другое не разрешено
CWG 1891 C++11 у закрытия был удалён конструктор по умолчанию и неявные конструкторы копирования/перемещения нет конструктора по умолчанию и конструкторы копирования/перемещения по умолчанию
CWG 1937 C++11 что касается эффекта вызова результата функции преобразования, не было указано, на какой объект вызов её operator() имеет тот же эффект на экземпляр типа закрытия, созданный по умолчанию
CWG 1973 C++11 список параметров типа закрытия operator() мог ссылаться на список параметров, указанный в trailing-type может ссылаться только на params
CWG 2011 C++11 для ссылки, захваченной по ссылке, не было указано, на какую сущность ссылается идентификатор захвата он ссылается на первоначально ссылаемую сущность
CWG 2095 C++11 поведение захвата ссылок на значения функций копированием не было ясно определено
CWG 2211 C++11 поведение было неопределенным, если у захвата было такое же имя, как и у параметра программа в этом случае неправильна
CWG 2358 C++14 лямбда-выражения, используемые в аргументах по умолчанию, должны были быть без захватов, даже если все захваты инициализированы выражениями, которые могут быть в аргументах по умолчанию разрешить такие лямбда-выражения с захватами
CWG 2509 C++17 каждый спецификатор мог иметь несколько повторений в последовательности спецификаторов каждый спецификатор может встречаться только один раз в последовательности спецификаторов
CWG 2542 C++11 не было ясно, является ли тип закрытия структурным он не является структурным
P0588R1 C++11 правило неявного захвата лямбда-выражения обнаружило odr-использование обнаружение упрощено

См. также

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

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

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

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/w/cpp/language/lambda

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API