跳转至

泛型 lambda

标准 难度 状态 分级 证据 审阅 最后校验
C++14 3/5 final A 3 条(manual 3) 单审(zyuhao7) 2026-10-27

编译器支持:GCC 4.9+ · Clang 3.4+ · MSVC 19.0+。版本查询:cppreference 编译器支持。

主张与证据

主张 支持证据
用 auto 参数的 lambda 充当处理多种区块类型的局部辅助函数 ev-mold-generic-lambda-push-001
用泛型 lambda 对不同整数宽度统一做哈希 ev-mold-generic-lambda-hash-002
用带参数包的泛型 lambda 遍历 tuple 并逐个绑定 SQL 参数 ev-drogon-generic-lambda-binder-003

本站证据页:ev-drogon-generic-lambda-binder-003 · ev-mold-generic-lambda-hash-002 · ev-mold-generic-lambda-push-001

解决什么问题

C++11 的 lambda 参数类型必须写死。要让一个回调同时处理 int、std::string 或任意用户类型,只能退回函数模板、手写仿函数,或用 std::function 加类型擦除。前两者把简单逻辑拆到离调用点很远的地方,后者带来运行期间接调用。

C++14 允许 lambda 参数写 auto。编译器为闭包生成一个带模板 operator() 的类,每个实参类型各实例化一份。写法仍然是一个内联表达式,泛型能力却和函数模板相同。标准提案为 N3649。

基础用法

参数位置的 auto 与函数模板的类型推导规则一致,引用与 const 需要显式写出。

#include <algorithm>
#include <iostream>
#include <string>
#include <utility>
#include <vector>

int main() {
  std::vector<int> numbers{1, 2, 3};
  std::vector<std::string> words{"a", "bb"};

  auto print = [](const auto& value) { std::cout << value << ' '; };
  std::for_each(numbers.begin(), numbers.end(), print);
  std::for_each(words.begin(), words.end(), print);
  std::cout << '\n';

  auto twice = [](auto value) { return value + value; };
  std::cout << twice(4) << ' ' << twice(std::string("ab")) << '\n';

  auto forward = [](auto&& value) -> decltype(auto) {
    return std::forward<decltype(value)>(value);
  };
  int x = 7;
  int& ref = forward(x);
  ref += 1;
  std::cout << x << ' ' << forward(9) << '\n';
}

输出 1 2 3 a bb(末尾有一个空格)、8 abab 与 8 9。print、twice 和 forward 分别是 const auto&、按值 auto 与转发引用 auto&& 三种参数写法;forward 用 decltype(auto) 保留引用,所以 ref += 1 改到了原变量。完整版本见 examples/cpp14/generic_lambda.cpp。

进阶用法

auto&& 是转发引用的简写,能同时绑定左值和右值。需要原样转发时写 [](auto &&value) { use(std::forward<decltype(value)>(value)); },只读访问则用 const auto &。

参数包同样适用:[](auto &&...args) 生成可变参数的模板调用运算符,配合 std::apply 或折叠表达式可以展开 tuple 与任意参数列表。

返回类型可以写 decltype(auto) 保留引用与值类别。普通 auto 会按值返回,可能丢掉引用或触发拷贝。

泛型 lambda 不能直接递归调用自己,因为闭包类型在定义完成前不可见。常见做法是把自身作为参数传入,或先用 std::function 包一层。C++23 的 deducing this 提供了更直接的写法。

每个实参类型都会生成独立实例。模板代码体积、编译时间和调试信息随类型数量增长,热路径上大量不同类型会显著增加二进制大小。

真实项目证据

项目 位置 用法 为什么典型
mold src/passes.cc#L93-L97 auto *x 参数的 lambda 登记多种 Chunk 派生类 同一段逻辑服务多种类型,省去重载
mold src/icf.cc#L236-L238 auto val 参数的 lambda 对任意整数宽度做哈希 只依赖字节表示,类型由调用点决定
drogon orm_lib/inc/drogon/orm/Criteria.h#L122-L127 auto &&...args 的 lambda 展开 tuple 并绑定 SQL 参数 参数包类型在调用点确定,泛型 lambda 负责展开

三条证据均为 manual 级:作者复核,尚未引入第二名审核者。mold 的两处用于局部辅助函数,drogon 的一处用于延迟绑定,覆盖了泛型 lambda 最常见的两种角色。

陷阱与边界

auto 参数按值推导。 [](auto value) 会复制实参,大对象应写 const auto & 或 auto &&。

引用与 const 不会自动保留。 与函数模板一样,传左值给 auto 参数得到副本,不会保持引用语义。

每个类型一份实例。 泛型 lambda 不是运行期多态;对不同类型调用会生成多份代码,类型越多编译越慢。

不能直接递归。 闭包类型在 lambda 体内尚不完整,需要显式传入自身或改用其它结构。

重载决议可能出人意料。 泛型 lambda 的 operator() 是模板,传入某些类型会参与模板推导并与其它重载竞争,错误信息通常较长。

捕获列表不参与泛型。 auto 只影响参数;捕获的变量类型仍然固定,按值捕获会在构造闭包时复制。

库联动

  • 标准算法:std::for_each、std::transform、std::visit 等直接接受泛型 lambda,无需包装成函数对象。
  • mold:链接器内部用泛型 lambda 处理多种区块和整数宽度,减少重复的辅助函数。
  • drogon ORM:延迟绑定 SQL 参数,用参数包泛型 lambda 展开任意实参类型。
  • stdexec:发送者适配器用模板 lambda 选择完成签名,把类型推导留在调用点。

横向对比

方案 定义位置 运行期开销 类型擦除 主要限制
泛型 lambda 调用点内联 无 否 每个类型实例化,不能直接递归
函数模板 命名空间作用域 无 否 逻辑远离调用点
手写仿函数 类作用域 无 否 样板代码多
std::function 任意 有间接调用 是 可能堆分配,丢失内联机会

跨标准演进:C++14 通过 N3649 引入泛型 lambda;C++20 允许模板参数列表与约束,C++23 的 deducing this 让递归与显式对象参数更自然。