Spec-Zone.ru › C++

Лямбда-выражения (начиная с C++11)

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

Синтаксис

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

Пояснение

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

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

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

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

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

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

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

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

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

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

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

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

Тип замыкания имеет следующие члены; их нельзя явно инстанцировать, явно специализировать или(начиная с 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 .

Если 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 в порядке появления добавляется вымышленный параметр шаблона. Вымышленный параметр шаблона может быть пакетом параметров, если соответствующий параметр функции-члена в 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)

Спецификация исключений except в лямбда-выражении применяется к 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.

  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 = /* 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)

Эта пользовательская функция преобразования определяется только в том случае, если у лямбда-выражения нет captures и нет явного параметра объекта(начиная с C++23). Это открытая, constexpr-,(начиная с C++17) невиртуальная, неявная, константная функция-член 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++, которая при вызове имеет тот же эффект, что и:

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

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

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

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

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

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

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)

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

(начиная с 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)

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

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

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) простой захват по значению, являющийся развёрткой пакета
3) захват по значению с инициализатором
4) простой захват по ссылке
5) простой захват по ссылке, являющийся развёрткой пакета
6) захват по ссылке с инициализатором
7) простой захват текущего объекта по ссылке
8) простой захват текущего объекта по значению
9) захват по значению с инициализатором, являющийся развёрткой пакета
10) захват по ссылке с инициализатором, являющийся развёрткой пакета

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

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]
}

Если capture-default — =, последующие простые захваты должны начинаться с & или быть *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
}

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

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

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

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

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

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

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

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

Поиск identifier в любом захвате без инициализатора (кроме захвата 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)

Если captures содержит capture-default и явно не захватывает охватывающий объект (как 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
}

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

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
    }();
}

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

#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
}

В теле лямбда-выражения со значением по умолчанию для захвата = тип любой захватываемой сущности определяется так, как если бы она была захвачена (поэтому часто добавляется квалификатор const, если лямбда-выражение не является mutable), даже если сущность находится в невычисляемом операнде и не захвачена (например, в 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&
    };
}

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

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

Если лямбда-выражение захватывает охватывающий объект (как 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) использует по правилу ODR *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
                };
            };
        }
    };
}

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

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
}

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

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

  • если охватывающее лямбда-выражение m1 захватывает по значению, m2 захватывает нестатический член типа замыкания m1, а не исходную переменную или *this; если m1 не является mutable, нестатический член данных считается имеющим квалификатор const.
  • если охватывающее лямбда-выражение 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
}

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

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

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

Если captures содержит capture-default и явно не захватывает охватывающий объект (как 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++.

Отчёт об ошибке Применён к Опубликованное поведение Исправленное поведение
CWG 974 C++11 аргумент по умолчанию не допускался в
списке параметров лямбда-выражения
разрешён
CWG 1048
(N3638)
C++11 тип возвращаемого значения можно было вывести только для тел лямбда-выражений,
содержащих единственный оператор return
улучшен вывод
типа возвращаемого значения
CWG 1249 C++11 не было ясно, считается ли захваченный член
охватывающего лямбда-выражения без mutable константным
считается константным
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 замыкание имело удалённый конструктор по умолчанию
и неявные конструкторы копирования/перемещения
нет конструктора по умолчанию; конструкторы
копирования/перемещения объявлены defaulted
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) задаёт тип, выводимый из выражения
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/cpp/language/lambda

Spec-Zone.ru

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