[2026] C++ Generic Lambdas — auto Parameters and Template Lambdas (C++20)

[2026] C++ Generic Lambdas — auto Parameters and Template Lambdas (C++20)

이 글의 핵심

This guide covers plain vs generic lambdas, how C++14 auto parameters turn operator() into a function template, C++20 template lambdas, practical STL patterns, type deduction rules, and performance considerations in one place.

What is a generic lambda?

A generic lambda is a C++14 feature: you put auto on a parameter so the same body can be reused for several types. Internally, the closure type’s operator() is a function template.

auto add = [](auto a, auto b) { return a + b; };
int x = add(1, 2);           // int
double y = add(1.5, 2.5);    // double

Why use it?

  • Less duplication: you do not need separate lambdas for int and double.
  • STL-friendly: easy to pass one comparison or predicate into std::sort, std::find_if, and similar.
  • C++20 onward: pairs naturally with the more explicit template lambda syntax.

Reading Lambda basics and auto type deduction first will make this article easier to follow.


Plain lambda vs generic lambda

Plain lambda (non-template operator())

If parameter types are fixed, the closure’s operator() is a single ordinary member function.

auto cmp = [](int a, int b) { return a < b; };
// Conceptually: struct __lambda { bool operator()(int a, int b) const; };

It fits one type; to use double or std::string you would need another lambda.

Generic lambda (auto parameters)

Using auto (or auto&, const auto&, etc.) on a parameter makes operator() a function template.

auto cmp = [](auto a, auto b) { return a < b; };
// Conceptually: struct __lambda {
//   template<typename T, typename U>
//   auto operator()(T a, U b) const { return a < b; }
// };

The same lambda object can sort and compare int, double, and user-defined types that define operator<.

AspectPlain lambdaGeneric lambda
ParametersConcrete typesauto / decltype(auto), etc.
operator()Non-templateFunction template
InstancesOneSpecializations as needed per call
ReadabilityClear when simpleSignals “multiple types allowed”

How auto parameters work

The C++14 rules boil down to this:

  1. For each auto parameter in a generic lambda, a distinct template type parameter is introduced.
  2. If the lambda’s return type is not specified, return type deduction follows rules similar to decltype(auto) from the body’s return statements (same idea as for ordinary lambdas).
[](auto x) { return x * 2; }   // A template instance per type of x
[](auto& x) { return x; }       // Reference: no copy for read/write

Caveat: plain auto is by value. For large objects, prefer const auto& or auto&& (forwarding-reference-like deduction). That lines up with perfect forwarding.

// Runnable example
std::vector<std::string> v = {"a", "b"};
std::for_each(v.begin(), v.end(), [](const auto& s) {
    std::cout << s << '\n';  // Avoid unnecessary copies
});

The compiler’s closure type is implementation-specific, but the key point is that operator() is a template. Template instantiations appear per call pattern, and like ordinary function templates they can be duplicated across TUs that are not merged—same trade-offs as templates generally.


C++20 template lambdas

C++20 lets you express the same idea with explicit template syntax.

auto f = []<typename T>(T x) { return x + x; };           // One type parameter
auto g = []<class T, class U>(T a, U b) { return a < b; };

Differences and benefits

  • Named type parameters: T is visible in the signature.
  • Easier to attach requires constraints (C++20 concepts) directly on the lambda.
auto clamp_positive = []<typename T>(T x) requires std::is_arithmetic_v<T> {
    return x > T{0} ? x : T{0};
};

With auto parameters only, constraining the anonymous template parameters is awkward; template lambdas pair well with requires. For syntax details see C++ template lambdas and generic lambdas and error messages.


Practical use: STL algorithms

Sorting and comparison

std::vector<std::pair<int, std::string>> items = {{2, "b"}, {1, "a"}};
std::sort(items.begin(), items.end(),
    [](const auto& a, const auto& b) { return a.first < b.first; });

You do not need to spell out the pair type for a sort on the first element.

auto it = std::find_if(vec.begin(), vec.end(),
    [](const auto& e) { return e.id == target_id; });

e is deduced as the container’s value_type.

Transform and accumulate

std::transform(a.begin(), a.end(), out.begin(),
    [](auto x) { return std::abs(x); });
int sum = std::accumulate(v.begin(), v.end(), 0,
    [](auto acc, const auto& x) { return acc + x.size(); });

std::visit and variants

std::visit([](const auto& x) { std::cout << x; }, my_variant);

One lambda body can handle each alternative of std::variant (a distinct operator() instantiation per alternative).


Type deduction rules

  1. Parameter auto: Same family as auto in a function template parameter—template arguments are deduced from the arguments at the call site.
  2. auto& / const auto&: Express intent to preserve lvalue-ness and constness. const auto& also binds to temporaries and is widely applicable.
  3. auto&&: Forwarding reference rules apply, so you can bind lvalues and rvalues efficiently (typical for “perfect forwarding” inside a generic lambda).
  4. Return type: If omitted, all return paths must deduce to a compatible type. Different types in branches (e.g. int vs double) cause a deduction conflict.
// Likely error: int vs double in different branches
// [](auto x) { return cond ? 0 : 1.0; }  // Deduction clash

When needed, give the lambda an explicit return type:

[](auto x) -> double { return x * 1.0; }

Performance considerations

  • Overhead: A lambda call is like a small inlineable function. Generics are still not virtual (standard lambdas are not polymorphic through vtables).
  • Code size: Each distinct argument-type combination can produce another template instance. Hundreds of distinct signatures can bloat the binary; sometimes a non-template function or a single-type lambda is better.
  • Capture: [=], [&], etc.—captured state affects closure size whether or not the lambda is generic.
  • Optimization: Compilers often inline comparison lambdas for std::sort aggressively. Do not assume “generic means slow”—measure before optimizing.

decltype and generic lambdas

For a parameter named x, decltype(x) reflects the actual argument type at the call. That is handy when you need a local “same type as x” in the body.

// Variable declaration and initialization
auto f = [](auto x) -> decltype(x) {
    decltype(x) copy = x;
    return copy;
};

Together with the decltype guide, this also clarifies differences from lambdas that use decltype(auto) as the return type.


constexpr generic lambdas (C++17)

If a lambda is constexpr and the body satisfies constexpr requirements, it can be evaluated at compile time. Generic lambdas behave the same: when arguments are literals (or otherwise constexpr-friendly), the corresponding template instance can be evaluated as a constexpr call.

constexpr auto sq = [](auto x) { return x * x; };
static_assert(sq(3) == 9);

Using non-literal-friendly types such as std::string as arguments usually forces runtime evaluation. See constexpr lambdas.


Common mistakes

  1. Only auto by value but you meant to mutate elements: You modify a copy; container elements stay unchanged. Use auto& or auto* as appropriate.
  2. Two different auto parameters: auto a, auto b are two independent template parameters. To force the same type, a C++20 template lambda with template<typename T> ... (T a, T b) is clearer.
  3. Recursion: A nameless lambda is awkward to call from inside itself. Consider std::function, a Y combinator pattern, or a named function object.

Summary

KeywordMeaning
Generic lambdaauto parameters → templated operator()
C++20Explicit template lambdas: []<typename T>(T x){}
STLLess type repetition in sort, search, transform, visit
PerformanceInline, non-virtual; instance count scales with types used

See also: Lambda capture, constexpr lambdas, decltype.


More reading