std::identity是C++20引入的零开销恒等函数对象,定义在<functional>中,其operator()完美转发参数,专为满足ranges算法对投影(proj)的concept约束(如indirectly_unary_invocable)而设计,必须用std::identity{}显式初始化并作为投影参数传入,不可省略花括号或替换为lambda。

std::identity 是什么,为什么能当投影用
std::identity 是 C++20 引入的函数对象,定义在 <functional> 中,它的作用就是原样返回传入的参数:operator()(T&& t) { return std::forward<t>(t); }</t>。它天然满足“投影”(projection)的要求:接受一个元素,返回一个可比较/可操作的值。所以只要算法支持 proj 参数(比如 std::ranges::sort、std::ranges::max_element),你就能用它替代手写 lambda 或冗余的 [] (auto&& x) { return x; }。
注意:它不是“默认”自动启用的——C++ 标准库中没有任何算法把 std::identity{} 设为 proj 的默认参数;你得显式传进去,否则就走无投影路径。
哪些算法支持 std::identity 作投影参数
支持 proj 的算法集中在 std::ranges 命名空间(C++20 起),常见有:
-
std::ranges::sort、std::ranges::stable_sort
-
std::ranges::find_if、std::ranges::count_if(但注意:这些对 proj 的语义是“先投影再判断”,不是所有场景都适用)
-
std::ranges::min_element、std::ranges::max_element
-
std::ranges::lower_bound、std::ranges::upper_bound(要求投影后结果可比较)
std::ranges::sort、std::ranges::stable_sort
std::ranges::find_if、std::ranges::count_if(但注意:这些对 proj 的语义是“先投影再判断”,不是所有场景都适用)std::ranges::min_element、std::ranges::max_element
std::ranges::lower_bound、std::ranges::upper_bound(要求投影后结果可比较)老式非 ranges 算法(如 std::sort)不接受 proj,强行传会编译失败。别混用。
std::identity 投影的典型用法与易错点
最常见场景是想对自定义类型按某个成员排序,但又不想写 lambda。例如:
struct Person {
std::string name;
int age;
};
std::vector<Person> v = {{"Alice", 30}, {"Bob", 25}};
std::ranges::sort(v, {}, &Person::age); // 成员指针投影 —— 这不是 std::identity
std::ranges::sort(v, {}, std::identity{}); // 按 Person 对象本身排序(需定义 operator<
关键区别:
立即学习“C++免费学习笔记(深入)”;
- 用
std::identity{}表示“我就要用这个对象本身参与比较”,所以Person必须可比较(比如有operator<或提供 comparator) - 如果想按
name排序但又懒得写[] (const auto& p) { return p.name; },那应该用std::ranges::sort(v, {}, &Person::name),而不是std::identity -
std::identity{}和不传proj并不等价:前者明确指定投影逻辑,后者可能触发不同重载(比如某些算法无proj版本只接受 comparator)
性能和兼容性要注意什么
std::identity 是零开销抽象:编译器几乎总能内联,生成的汇编和手写 return x; 一样。但它依赖 C++20,若项目还在用 C++17,不能直接用 —— 此时可自己仿一个轻量版:
struct identity {
template<class T>
constexpr T&& operator()(T&& t) const noexcept { return std::forward<T>(t); }
};
不过要注意:标准 std::identity 支持 const 重载和 SFINAE 友好,自定义版若没处理好 const 成员访问或完美转发边界,可能在复杂模板上下文中出问题。
真正容易被忽略的是:当你把 std::identity{} 传给一个期望投影返回 const 类型的算法(比如某些需要 const T& 比较的自定义 range adaptor),编译器可能因 cv-qualifier 不匹配而报错,这时得检查投影返回值类型是否和 comparator 期望一致。


















