лямбды и вывод типовlambdas and type deduction

generic lambdas

Параметр лямбды с auto вместо конкретного типа — компилятор генерирует шаблонный operator(), и одна лямбда работает с любым типом, для которого её тело корректно компилируется.A lambda parameter declared with auto instead of a concrete type — the compiler turns the closure's operator() into a template, and one lambda works with any type for which its body compiles.

часто используетсяcommonly used не C++11 — частая путаница на собеседованииnot C++11 — a common interview mix-up

Что этоWhat it is

До C++14 параметры лямбды требовали конкретного типа, как у обычной функции. C++14 разрешает писать auto вместо типа параметра — компилятор превращает operator() замыкающего класса лямбды в шаблонную функцию-член, параметризованную по каждому такому auto-параметру independently.Before C++14, lambda parameters required a concrete type, just like an ordinary function. C++14 allows writing auto instead of a parameter type — the compiler turns the closure class's operator() into a templated member function, parameterized independently over each such auto parameter.

C++11 — лямбда с конкретным типомC++11 — lambda with a concrete type
auto add_int = [](int a, int b) { return a + b; };
// нужна отдельная лямбда на каждый тип:// a separate lambda is needed for every type:
auto add_double = [](double a, double b) { return a + b; };
C++14 — один generic-параметр на все типыC++14 — one generic parameter for every type
auto add = [](auto a, auto b) { return a + b; };
add(1, 2);        // operator()<int, int>
add(1.5, 2.5);    // operator()<double, double> — отдельная инстанциация// operator()<double, double> — a separate instantiation

Зачем ввелиWhy it was introduced

Ограничение C++11-лямбд на конкретные типы параметров плохо сочеталось с остальным духом обобщённого программирования в STL:The C++11 restriction of lambdas to concrete parameter types clashed with the generic-programming spirit of the rest of the STL:

01

Лямбды для STL-алгоритмов часто дублировались по типамLambdas for STL algorithms were often duplicated per type

Компаратор, предикат или accumulator-функция, логически одинаковые для int, double, std::string, приходилось писать заново под каждый тип или заменять громоздким шаблонным функтором — то, для чего изначально задумывались лямбды как более лёгкая альтернатива.A comparator, predicate, or accumulator function that was logically identical for int, double, and std::string had to be rewritten for every type, or replaced by a bulky templated functor — exactly what lambdas were meant to be a lighter alternative to in the first place.

02

Несогласованность с auto-параметрами уже существующих шаблонных функцийInconsistency with the auto-like parameters of existing template functions

Обычная шаблонная функция template<class T> T add(T a, T b) уже работала с любым типом — лямбда была единственным местом в языке, где обобщённость была недоступна без явного написания класса-функтора с шаблонным operator() вручную.An ordinary template function template<class T> T add(T a, T b) already worked with any type — the lambda was the one place in the language where genericity was unavailable without hand-writing a functor class with a templated operator().

03

Устранение ручного написания функторов с шаблонным operator()Eliminating hand-written functors with a templated operator()

До generic lambdas обобщённый callable-объект писался как полноценный класс с шаблонным operator() — эквивалент того, что теперь можно выразить одной строкой лямбды с auto-параметрами.Before generic lambdas, a generic callable had to be written as a full class with a templated operator() — the equivalent of what can now be expressed in a single lambda line with auto parameters.

Как это работает под капотомHow it works under the hood

Лямбда-выражение — это всегда синтаксический сахар над безымянным классом (замыканием) с перегруженным operator(). Для обычной (не generic) лямбды этот operator() — обычная функция-член с конкретными типами параметров. Для generic-лямбды каждый параметр, помеченный auto, становится независимым шаблонным параметром operator() — фактически компилятор генерирует:A lambda expression is always syntactic sugar over an unnamed class (the closure) with an overloaded operator(). For an ordinary (non-generic) lambda, that operator() is a plain member function with concrete parameter types. For a generic lambda, each parameter marked auto becomes an independent template parameter of operator() — the compiler effectively generates:

// исходный код:// source code:
auto add = [](auto a, auto b) { return a + b; };

// компилятор генерирует эквивалент (концептуально):// the compiler generates the equivalent (conceptually):
struct __lambda_1 {
    template <class T1, class T2>
    auto operator()(T1 a, T2 b) const {
        return a + b;
    }
};

Ключевое следствие: T1 и T2 — независимые типы, даже если оба параметра написаны как auto — вызов add(1, 2.5) инстанциирует operator()<int, double>, а не требует одинакового типа для обоих аргументов. Каждый уникальный набор типов аргументов при вызове порождает свою инстанциацию operator(), как и для любого шаблона.The key consequence: T1 and T2 are independent types even though both parameters are written as auto — calling add(1, 2.5) instantiates operator()<int, double> rather than requiring both arguments to share a type. Every unique combination of argument types at a call site produces its own instantiation of operator(), exactly as with any template.

ГраблиGotchas

auto-параметры лямбды — не тот же auto, что тип лямбды-переменнойThe lambda's auto parameters aren't the same auto as the lambda variable's type

auto f = [](auto x) {...}; — здесь два разных использования auto: внешний выводит тип самой переменной f (уникальный тип замыкания), внутренний — превращает operator() в шаблон. Путать их означает неверно объяснять механизм на собеседовании.auto f = [](auto x) {...}; — here auto is used twice for different purposes: the outer one deduces the type of the variable f itself (the closure's unique type), the inner one turns operator() into a template. Conflating the two means misexplaining the mechanism in an interview.

Не путать с C++11 — частый вопрос на знание границ стандартовDon't confuse it with C++11 — a common question testing knowledge of standard boundaries

Генерик-лямбды часто ошибочно приписывают C++11 наравне с обычными лямбдами (см. cpp11/lambda.html) — сами лямбда-выражения появились в C++11, но параметры auto в них — исключительно C++14.Generic lambdas are often mistakenly attributed to C++11 alongside ordinary lambdas (see cpp11/lambda.html) — lambda expressions themselves appeared in C++11, but auto parameters in them are C++14-only.

Каждая уникальная комбинация типов — отдельная инстанциация, отдельный кодEvery unique type combination is a separate instantiation, separate code

Как и с любым шаблоном, generic-лямбда, вызванная с N разными комбинациями типов аргументов, породит N отдельных инстанциаций operator() в скомпилированном коде — раздутие размера бинарника при большом разнообразии типов вызова, аналогично template bloat у обычных шаблонных функций.As with any template, a generic lambda called with N different argument-type combinations produces N separate operator() instantiations in the compiled code — binary bloat under a wide variety of call-site types, the same phenomenon as template bloat for ordinary template functions.