Generic Lambdas
C++14 allows lambda parameters to use auto, turning the lambda's operator() into an implicit function template β a single lambda can accept arguments of different types
| Book | Video | Code | X |
|---|---|---|---|
| cppreference-lambda / markdown | Video Explanation | Exercise Code |
Why introduced?
- In C++11, lambda parameter types must be explicitly specified β the same lambda cannot be reused for different argument types because
operator()is a plain member function, not a template - Many lambdas express type-independent logic (e.g. comparing two values), but C++11 required writing separate lambdas for each concrete type (e.g.
[](int a, int b) { return a < b; }for int,[](double a, double b) { return a < b; }for double) - C++14 allows
autoin lambda parameters; the compiler generates an implicit template foroperator(), essentially bringing function templates into the lambda world
How does it work?
The compiler expands a generic lambda into a functor class with a templated operator(). For example, [](auto a, auto b) { return a + b; } is internally equivalent to:
struct __lambda {
template <typename T1, typename T2>
auto operator()(T1 a, T2 b) const {
return a + b;
}
};
I. Basic Usage and Scenarios
Simple Generic Lambda
Declare lambda parameters with auto; the compiler generates operator() instances based on the argument types at the call site
auto identity = [](auto x) {
return x;
};
int i = identity(42); // x deduced as int
double d = identity(3.14); // x deduced as double
Multi-Parameter Generic Lambda
auto add = [](auto a, auto b) {
return a + b;
};
add(1, 2); // int + int
add(1.5, 2.5); // double + double
add(std::string("hello "), std::string("world")); // string + string
Each parameter's type is deduced independently; T1 and T2 can differ:
auto multiply = [](auto a, auto b) {
return a * b;
};
multiply(2, 3.5); // int * double β double
Generic Lambda + STL Algorithms
The most common use case β avoid writing identical logic for every container element type:
std::vector<int> v1 = {5, 1, 4, 2, 8};
std::vector<double> v2 = {3.1, 2.7, 8.5, 1.9};
// C++11: write separate lambdas for int and double
std::sort(v1.begin(), v1.end(), [](int a, int b) { return a > b; });
std::sort(v2.begin(), v2.end(), [](double a, double b) { return a > b; });
// C++14: a single generic lambda handles both
auto gt = [](auto a, auto b) { return a > b; };
std::sort(v1.begin(), v1.end(), gt);
std::sort(v2.begin(), v2.end(), gt);
Generic Lambda with Captures
Captured variables keep their concrete types; only parameters use auto:
int threshold = 10;
auto above = [threshold](auto x) {
return x > threshold; // threshold is int, x is generic
};
above(20); // x = int
above(3.5); // x = double
Generic Lambda Returning Lambda
A generic lambda can return a new lambda, creating a function factory:
auto make_adder = [](auto n) {
return [n](auto x) { return x + n; }; // C++14 supports this
};
auto add5 = make_adder(5);
add5(10); // 15
add5(3.14); // 8.14
II. Real-World Case β Transparent Functors and Generic Lambdas in the STL
C++14 introduced transparent operator functors (e.g.
std::less<>/std::greater<>) alongside generic lambdas β both motivated by the same goal: eliminating the need to hard-code concrete types. The examples below cite the vendored MSVC STL (source:msvc-stl/stl/inc/xutility);_NODISCARD/constexprare internal annotations and can be ignored while reading
std::greater Transparent Specialization β A Comparator That "Sees" Any Type
In C++11, std::greater<T> locked in the template parameter T β std::greater<int> could only compare int. C++14 added std::greater<> (equivalent to std::greater<void>), where operator() is itself a template accepting any comparable types:
// MSVC STL Β· msvc-stl/stl/inc/xutility (abridged)
template <>
struct greater<void> {
template <class _Ty1, class _Ty2>
_NODISCARD constexpr auto operator()(_Ty1&& _Left, _Ty2&& _Right) const
noexcept(...) -> decltype(static_cast<_Ty1&&>(_Left) > static_cast<_Ty2&&>(_Right)) {
return static_cast<_Ty1&&>(_Left) > static_cast<_Ty2&&>(_Right);
}
using is_transparent = int;
};
The operator() is a template member function β exactly the same underlying mechanism as a generic lambda. The is_transparent tag signals to associative containers like std::set and std::map that this comparator supports heterogeneous lookup, allowing a std::string to be used to search a std::set<std::string> without constructing a temporary object
Transparent Comparators and Generic Lambdas β Two Sides of the Same Coin
Transparent functors and generic lambdas are interchangeable for the same use case:
std::vector<int> v = {5, 1, 4, 2, 8};
// option 1: C++14 transparent comparator
std::sort(v.begin(), v.end(), std::greater<>());
// option 2: C++14 generic lambda
std::sort(v.begin(), v.end(), [](auto a, auto b) { return a > b; });
Both eliminate the C++11 redundancy of writing separate comparators for int, double, string, etc.
Summary: C++14's transparent functors and generic lambdas are two expressions of the same idea β "parameterize the argument types". Transparent functors apply it to callable objects; generic lambdas apply it to lambdas. Understanding how the
is_transparenttag connects both to STL algorithms shows why the standard library introduced these two features together in C++14
III. Notes
A Generic Lambda Is a Class with a Templated operator()
Each generic lambda expression produces a distinct closure type. Even two identical-looking generic lambdas have different types β the same rule as regular lambdas, but the operator() is a template, so the same type can accept different argument types
auto f = [](auto x) { return x; };
auto g = [](auto x) { return x; };
// f and g have different types; cannot be assigned to each other
Generic Parameters and Perfect Forwarding
Generic lambda parameter deduction strips references and const by default. Use auto&& with std::forward to preserve them:
auto forwarder = [](auto&& x) -> decltype(auto) {
return std::forward<decltype(x)>(x);
};
This pattern is common with generic lambdas and is a typical use case for decltype(auto) (another C++14 feature)
Generic Lambdas Can Be Variadic
C++14 generic lambdas support parameter packs β [](auto... xs) accepts any number (and types) of arguments. The compiler generates a templated operator() with a parameter pack. C++20 later added explicit template parameter lists for lambdas ([]<typename... Ts>(Ts... xs)), but variadic generic lambdas were already usable in C++14
IV. Exercise Code
Exercise Code Topics
- 0 - Basic Generic Lambda β auto parameters and type deduction
- 1 - Generic Lambda with STL Algorithms β sort, find, factory function
Exercise Auto-Checker Command
Don't have d2x? Click for setup
# 1. Install xlings (Linux / macOS)
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash
# Windows PowerShell:
# irm https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.ps1 | iex
# 2. Install d2x and get the tutorial
xlings install d2x -y
d2x install d2mcpp
# 3. Enter the project directory & run the checker
cd d2mcpp
d2x checker generic-lambdas