std::identity 是 C++20 引入的恒等函数对象,用于投影参数中表示“不修改原值”,在需显式指定投影但无需变换时(如 std::ranges::sort)作为零开销默认投影器。

std::identity 是什么,它在投影中起什么作用
std::identity 是 C++20 引入的函数对象,定义在 <functional> 头文件里。它不做任何转换,直接返回传入的参数 —— 本质就是 auto operator()(T&& t) const noexcept { return std::forward<t>(t); }</t>。它常被用作“默认投影器”,尤其在需要显式指定投影参数但又不想修改原始值的场景下,比如 std::ranges::sort、std::ranges::find_if 或 std::ranges::max_element 等算法。
什么时候必须显式传 std::identity 作为投影
当算法接受投影(projection)参数,且你操作的是自定义类型或结构体,但想按某个成员(比如 .id)排序/查找时,投影是必需的;而如果你只是想按整个对象本身比较(即不投影),却仍需满足函数签名要求,std::identity{} 就是安全、标准、零开销的选择。
- 例如:
std::ranges::sort(vec, std::less{}, std::identity{})—— 明确告诉编译器:用std::less比较,但投影函数是恒等映射 - 省略投影参数可能触发重载歧义,尤其当容器元素类型支持多种比较方式(如既有
operator<又有自定义compare) - 某些算法(如
std::ranges::unique)的投影参数不可省略(C++20 起),此时std::identity{}是最自然的占位符
std::identity 和 lambda([](auto&& x){return x;}) 有什么区别
语义相同,但行为细节不同:
-
std::identity{}是标准库定义的可复制、可默认构造、无状态的函数对象,满足invocable和regular_invocable约束,能被 SFINAE 或 concept 检查准确识别 - lambda
[](auto&& x) { return x; }在多数情况下工作正常,但它不是标准定义的投影器,某些高度泛化的模板(如某些 range adaptor 实现)可能依赖std::identity的类型特征做特化 - 性能上无差异 —— 两者都内联且零开销,但
std::identity更明确表达了“此处无需变换”的设计意图 - 注意:不要写成
[](auto x) { return x; }(按值捕获),会引发不必要的拷贝;而std::identity总是完美转发
常见误用和编译错误
最典型的错误是把 std::identity 当作类型名使用,或忘记加花括号构造实例:
立即学习“C++免费学习笔记(深入)”;
- ❌
std::ranges::sort(v, {}, std::identity);—— 错误:std::identity是类模板,不是变量;必须用std::identity{}构造对象 - ❌
std::ranges::sort(v, {}, std::identity{}());—— 错误:多写了括号,变成调用而非传递函数对象 - ❌ 在 C++17 或更早标准下使用 ——
std::identity不存在,会报‘identity’ is not a member of ‘std’ - ⚠️ 投影函数返回类型必须与比较谓词兼容:比如用
std::less<int>{}作比较器,投影应返回可比为int的类型;std::identity{}返回原类型,所以前提是该类型本身支持对应比较
非修改式投影的核心在于“不改变数据,只指定视角”——std::identity{} 就是这个视角的空操作表达,它不隐藏逻辑,也不引入副作用,但容易因语法细节翻车。


















