std::identity 是 c++20 引入的零开销函数对象,调用 std::identity{}(x) 直接返回 x,用于显式启用 ranges 算法的投影重载,确保类型匹配与接口统一。

std::identity 是什么,为什么它能当投影用?
它就是个空操作的函数对象,调用 std::identity{}(x) 直接返回 x 本身。C++20 引入投影(projection)参数后,很多算法(比如 std::ranges::sort、std::ranges::max_element)允许你传一个可调用对象,把每个元素“映射”成用于比较或处理的值。这时候 std::identity{} 就是那个“不映射”的安全选择——不是写 [] (auto& x) { return x; },而是标准库提供的、零开销、类型明确、语义清晰的方案。
哪些算法支持 std::identity 投影?
主要是 std::ranges 下带投影参数的算法,常见有:
-
std::ranges::sort、std::ranges::stable_sort -
std::ranges::min_element、std::ranges::max_element -
std::ranges::find_if(注意:这里投影用于谓词前的转换,不是所有场景都适用) -
std::ranges::unique(配合二元谓词时,投影决定“相等性依据”)
老式 std::sort(非 ranges 版)不接受投影,强行传会编译失败。别混用。
std::identity 和直接不传投影的区别?
区别很大,不是“可有可无”:
立即学习“C++免费学习笔记(深入)”;
- 不传投影 → 调用的是无投影重载,比如
std::ranges::sort(r, comp),此时comp直接作用于元素本身 - 显式传
std::identity{}→ 调用的是带投影重载,比如std::ranges::sort(r, comp, std::identity{}),此时comp作用于std::identity{}(a)和std::identity{}(b),也就是还是a和b,但签名和语义已切换
关键影响在于:有些算法只提供带投影的重载(比如某些自定义容器适配器),或者你需要统一接口风格(比如模板函数里统一用投影参数)。这时不用 std::identity{},编译就过不去。
示例:
std::vector<std::string> v = {"hello", "world", "cpp"};
std::ranges::sort(v, {}, std::identity{}); // ✅ 显式投影,合法
而下面这行会编译失败(因为 {} 是默认比较器,但没给投影,匹配不到带 projection 的重载):
std::ranges::sort(v, {}); // ❌ 编译错误:no matching function
容易踩的坑:类型推导与 const 限定
std::identity 本身不改变 const 性质,但它对引用类型的处理很敏感:
- 如果容器元素是
const int,std::identity{}(x)返回const int&,后续比较可能因 cv 限定不匹配失败 - 投影函数返回类型必须能被比较器接受;若你用了自定义
comp,它参数类型得跟投影结果一致,而不是原始元素类型 - 别写成
std::identity()(带括号调用),那是个临时对象调用,语法错;必须是std::identity{}或std::identity()作为类型(如decltype(std::identity{}))
最稳妥写法永远是 std::identity{} —— 它是字面量,零成本,且在所有支持 C++20 ranges 的标准库实现中行为一致。
复杂点在于:投影不是“透明转发”,它是算法逻辑的一部分。哪怕你用 std::identity{},也意味着你主动选择了投影路径,编译器会按该路径做类型推导和重载决议。漏掉它,或类型不匹配,往往报错信息又长又绕,直奔模板展开底层,实际问题却只是少了个大括号。










