лямбды и вывод типовlambdas and type deduction
[]<class T>(T x) { ... } — явный шаблонный параметр лямбды, дающий прямой доступ к имени типа T вместо обходного пути через decltype(x) из C++14 generic lambdas.[]<class T>(T x) { ... } — an explicit lambda template parameter, giving direct access to the name of type T instead of the decltype(x) workaround from C++14 generic lambdas.
C++20 позволяет лямбде объявлять явный шаблонный параметр в угловых скобках перед списком аргументов: []<class T>(T x) { ... } вместо неявного [](auto x) { ... } из C++14 (см. ../cpp14/generic-lambdas.html). Явный параметр даёт доступ к имени типа T прямо внутри тела лямбды — полезно для T{}-конструкций значения по умолчанию, sizeof(T), std::is_same_v<T, int> и подобных случаев, где нужен именно тип, а не просто значение параметра. Фича также поддерживает variadic template-параметры: []<class... Ts>(Ts... args) { ... }.C++20 lets a lambda declare an explicit template parameter in angle brackets before its argument list: []<class T>(T x) { ... } instead of C++14's implicit [](auto x) { ... } (see ../cpp14/generic-lambdas.html). The explicit parameter gives access to the name of type T directly inside the lambda's body — useful for T{} default-value construction, sizeof(T), std::is_same_v<T, int>, and similar cases that need the type itself, not just the parameter's value. The feature also supports variadic template parameters: []<class... Ts>(Ts... args) { ... }.
auto make_default = [](auto x) {
using T = decltype(x); // обходной путь, чтобы получить имя типа// a workaround to obtain the type's name
return T{};
};
// не работает, если тип нужен без значения параметра// doesn't work if the type is needed without a parameter value
auto make_default = []<class T>(T x) {
return T{}; // T доступен напрямую по имени// T is directly accessible by name
};
// variadic template-параметры лямбды:// variadic lambda template parameters:
auto sum_all = []<class... Ts>(Ts... args) {
return (args + ...);
};
C++14 generic lambdas (auto-параметры) не давали прямого доступа к выведенному имени типа — единственным способом получить его было using T = decltype(x); внутри тела. Это работает, только если уже есть значение параметра, из которого можно вывести тип — для случаев, где нужен именно тип как таковой (например, для создания значения по умолчанию до использования какого-либо конкретного x), этот приём не годится напрямую.C++14 generic lambdas (auto parameters) gave no direct access to the deduced type name — the only way to get it was using T = decltype(x); inside the body. That only works if a parameter value already exists to deduce the type from — for cases needing the type itself (say, constructing a default value before using any particular x), this trick doesn't directly apply.
Проверки вроде std::is_same_v<T, int>, sizeof(T), статические члены типа, вложенные typedef'ы — всё это требует имени типа как первоклассной сущности в выражении, а не выведения его косвенно через decltype от переменной. Явный шаблонный параметр лямбды даёт это имя напрямую, тем же способом, что и в обычной шаблонной функции.Checks like std::is_same_v<T, int>, sizeof(T), a type's static members, nested typedefs — all of these need the type's name as a first-class entity in an expression, not one deduced indirectly via decltype on a variable. An explicit lambda template parameter provides that name directly, the same way an ordinary template function does.
Обычные функции уже давно пишутся как template<class T> void f(T x) с явным именем параметра — лямбда была единственным местом, где эквивалентная генеричность выражалась неявно через auto, без возможности назвать сам параметр шаблона. C++20 устраняет эту асимметрию.Ordinary functions have long been written as template<class T> void f(T x) with an explicitly named parameter — the lambda was the one place where equivalent genericity was expressed implicitly via auto, with no way to name the template parameter itself. C++20 removes this asymmetry.
Как и C++14 generic lambda, лямбда с явным шаблонным параметром компилируется в замыкание с шаблонным operator() — сам механизм генерации кода не меняется. Разница исключительно в синтаксисе объявления шаблонного параметра: вместо того чтобы компилятор сам придумывал безымянный параметр шаблона для каждого auto, программист называет его явно в угловых скобках перед списком параметров функции, ровно как в обычном шаблоне.Like a C++14 generic lambda, a lambda with an explicit template parameter compiles down to a closure with a templated operator() — the underlying code-generation mechanism doesn't change. The difference is purely syntactic: instead of the compiler inventing an unnamed template parameter for each auto, the programmer names it explicitly in angle brackets before the function's parameter list, exactly as in an ordinary template.
// исходный код:// source code:
auto l = []<class T>(T x) { return sizeof(T) + sizeof(x); };
// компилятор генерирует эквивалент (концептуально):// the compiler generates the equivalent (conceptually):
struct __lambda_2 {
template <class T>
auto operator()(T x) const { return sizeof(T) + sizeof(x); }
};
// с C++14 auto-параметром пришлось бы писать так:// with a C++14 auto parameter you'd have had to write:
auto l2 = [](auto x) {
using T = decltype(x);
return sizeof(T) + sizeof(x); // тот же результат, но опосредованно// the same result, but indirectly
};
Явный параметр T особенно окупается, когда он нужен не для вывода из значения аргумента, а как самостоятельная сущность внутри тела — например, для построения std::vector<T> из отдельных элементов типа T, или как ограничение через concept прямо в списке шаблонных параметров лямбды, без отдельного requires-предложения:The explicit parameter T pays off especially when it's needed not by deducing it from an argument value but as a first-class entity inside the body — for instance, to build a std::vector<T> out of individual elements of type T, or as a concept constraint written directly in the lambda's template parameter list, with no separate requires clause:
// T используется для std::vector<T> в теле, не только для типа параметра:// T is used for std::vector<T> in the body, not just as the parameter's type:
auto repeat_n = []<class T>(T value, std::size_t n) {
return std::vector<T>(n, value); // с decltype(value) пришлось бы вводить лишний using// with decltype(value) an extra using would be needed
};
// constraint через concept прямо на параметре лямбды:// a concept constraint directly on the lambda's parameter:
auto safe_divide = []<std::integral T>(T a, T b) {
return b != 0 ? a / b : T{0};
};
// safe_divide(1.5, 2.0) — ошибка компиляции: double не удовлетворяет std::integral// safe_divide(1.5, 2.0) — compile error: double doesn't satisfy std::integral
Компаратор, использующий T для промежуточного значения (не только для типа параметров) — например, приведение обоих аргументов к общему типу перед сравнением.A comparator that uses T for an intermediate value (not just as the parameters' type) — for instance, converting both arguments to a common type before comparing.
auto by_abs = []<class T>(T a, T b) {
return std::abs(a) < std::abs(b);
};
std::ranges::sort(values, by_abs);
Суммирование произвольного числа аргументов разных типов через fold expression — естественное расширение того же синтаксиса на несколько типов сразу.Summing an arbitrary number of arguments of different types via a fold expression — a natural extension of the same syntax to several types at once.
auto sum_all = []<class... Ts>(Ts... args) {
return (args + ...);
};
auto total = sum_all(1, 2.5, 3); // Ts = {int, double, int}// Ts = {int, double, int}
Ограничение прямо в списке шаблонных параметров лямбды — ошибка компиляции при неподходящем типе видна сразу в точке вызова, без отдельного requires-предложения.The constraint sits directly in the lambda's template parameter list — a compile error for an unsuitable type shows up right at the call site, with no separate requires clause.
auto bit_count = []<std::integral T>(T x) {
return std::popcount(static_cast<std::make_unsigned_t<T>>(x));
};
bit_count(42); // ок, int удовлетворяет std::integral// ok, int satisfies std::integral
// bit_count(4.2); // ошибка компиляции — double не integral// bit_count(4.2); // compile error — double isn't integral
Задача: создать значение по умолчанию нужного типа. С generic lambda (см. ../cpp14/generic-lambdas.html) нужен обходной decltype; явный T убирает этот шаг целиком.The task: create a default value of the needed type. With a generic lambda (see ../cpp14/generic-lambdas.html) a decltype workaround is required; an explicit T removes that step entirely.
// было — C++14, decltype нужен, чтобы вообще назвать тип:// before — C++14, decltype is required just to name the type at all:
auto make_default_14 = [](auto proto) {
using T = decltype(proto);
return T{};
};
// стало — C++20, T назван явно, decltype не нужен:// after — C++20, T is named explicitly, no decltype needed:
auto make_default_20 = []<class T>(T proto) {
return T{};
};
[]<class T>(T x) и [](auto x) в большинстве простых случаев эквивалентны по инстанциации и производительности — оба порождают шаблонный operator(), оба инстанциируются заново на каждую уникальную комбинацию типов аргументов. Разница чисто в том, что T доступен по имени напрямую, а не только через decltype — не стоит путать это с "более эффективным" механизмом, это тот же механизм с более удобным способом назвать параметр.[]<class T>(T x) and [](auto x) are equivalent in instantiation and performance in most simple cases — both produce a templated operator(), both get re-instantiated for every unique combination of argument types. The difference is purely that T is accessible by name directly rather than only via decltype — don't mistake this for a "more efficient" mechanism; it's the same mechanism with a more convenient way to name the parameter.
Явный шаблонный параметр лямбды нельзя частично специализировать так же гибко, как обычный шаблон функции или класса — некоторые продвинутые метапрограммные паттерны (частичная специализация, SFINAE через несколько перегрузок с одинаковым именем) всё ещё требуют обычной именованной функции или класса, а не лямбды, даже в C++20.A lambda's explicit template parameter can't be partially specialized as flexibly as an ordinary function or class template — some advanced metaprogramming patterns (partial specialization, SFINAE across several overloads sharing a name) still require an ordinary named function or class, not a lambda, even in C++20.
С auto-параметром получение "чистого" имени типа для конструкций вроде значения по умолчанию требует лишнего шага (using T = decltype(x);), и легко забыть про ссылочные квалификаторы, если параметр объявлен как auto&& — decltype(x) в этом случае может дать неожиданную ссылку там, где нужен был просто тип значения. Явный шаблонный параметр той же лямбды делает эту деталь видимой сразу в сигнатуре.With an auto parameter, obtaining the "plain" type name for constructs like a default value takes an extra step (using T = decltype(x);), and it's easy to forget reference qualifiers if the parameter is declared as auto&& — decltype(x) in that case can yield an unexpected reference where a plain value type was wanted. An explicit template parameter on the same lambda makes this detail visible right in the signature.