auto cmp = [](int x, int y) { return x*x < y*y; }; лямбда - название для экспрешена (prvalue) тип этого экспрешена - замыкание cmp - объект замыкания cmp - некоторая переменная, у неё есть некоторый тип, сгенерированный компилятором, у которого определен оператор () от двух интов, который делает то, что в {} под каждую лямбду тип генерируется свой

то, что генерирует компилятор для лямбды называется замыканием (closure) тогда тип cmp - некоторое замыкание тип выводится по обычным правилам вывода типа (по умолчанию возвращаемый тип auto) если нам нужно явно указать тип возвращаемый лямбдой, то воспользуемся trailing return type (например, чтоб возвращать ссылку, можно -> decltype(auto))

до C++20 у closure type не было конструктора по умолчанию и так делать было нельзя: надо было передавать объект компаратора в конструктор явно: std::set<int, decltype(cmp)> s(cmp); (объекты лямбд были copy constructible, но не default contructible) а ещё если есть captures, то default ctor = deleted

std::set<int, decltype([](int x, int y) {return x*x < y*y; })> s - валидно

если лямбда без аргументов, то можно не писать () auto helloworld = [] { std::cout << "Hello world!\n"; }; helloworld();

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

[] { std::cout << "Hello world\n"; }(); - immediate invocation (создали и сразу же вызвали)

[](){}();- корректный код (первые () опционально)

если какие-то сложные вычисления, результатом которого должна быть константа, можно сделать immediate invocation

ещё где можно использовать immediate invocation: member initializer list, если нужно привязать ссылку к какому-то объекту после вычисления

captures in labdas

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

в теле лямбды нет доступа к локальным переменным, там видны лишь глобальные переменные и параметры лямбды чтобы они стали видны, их нужно захватить в [] указываем то, к чему мы хотим иметь доступ из лямбды

переменная захватывается по значению и его нельзя менять это потому, что оператор() константный, а переменная - поле лямбды захваченная переменная в closure хранится копией в самом объекте как поле тип этого объекта выводится немного по другим правилам auto - при передаче по значению внешний слой cv не отбрасывается если сделать capture with init, то вывод будет по правилам auto если принять константный объект по значению, он ляжет константным полем. таким образом даже сделав mutable лямбду, мы не сможем менять этот объект внутри лямбды, тк он const, а оператор() не const

чтоб не копировать объект, нужно какбы “принять его адрес” теперь в поле будет неконстантная ссылка на него теперь мы можем менять эту переменную константным оператором(), потому что сама ссылка не будет меняться, а внешний объект под ней да

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

здесь каждый вызов лямбды будет добавлять h к sub, тк поле sub для объекта одно

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

capture with initialization

в [] можно писать экспрешены. можно захватить нечто измененное, например [sub = sub + 'g'] левый sub будет названием поля, а правый sub возьмётся из локальной области видимости sub = std::move(sub) - захват с помощью мува, а не копирования (“захват по rvalue&”) &sub = std::as_const(sub) - “захват по константной ссылке” (as_const принимает объект, возвращает константную ссылку на него)

можно захватить сразу все локальные переменные по значению, то нужно написать [=] можно захватить всё по ссылке - [&] но это плохой кодстайл если захватить всё, то в полях лямбды будут только те переменные, которые реально использовались в теле

можно захватывать пакет в лямбду

захваченный пакет с инициализацией:

если мы хотим захватить поля класса (они не локальные переменные), то нужно сделать capture this и в лямбде тогда хранится this*, через который происходит доступ к его полям чтобы не было UB, нужно скопировать a в лямбду auto f = [a = a]

самая большая опасность использования лямбд - нужно всегда помнить, что лямбда не должна переживать то, что она захватила

переменные с static storage duration (static/глобальные) не подлежат захвату. они доступны без всякого захвата если мы хотим взять копию от такой, то делаем захват с инициализацией [copy = global]

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

copy ctor = default у лямбд нет copy assignment оператора

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

обобщенные лямбды (templated lambdas)

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

такие лямбды нельзя привести к указателю на функцию

можно писать requires к обощенным лямбдам так: более человечески:

можно добавлять атрибуты к лямбдам, пишутся перед trailing type, добавляют атрибут к оператор()

recursive lambdas

с C++23 с помощью deducing this можно писать рекурсивные лямбды

лямбду прям вот так можно подавать в параметр шаблона

std;;function