std::identity{}是std::ranges算法中唯一满足标准约束、支持constexpr且深度优化的恒等投影;必须用花括号初始化,否则编译失败,lambda或省略写法均不满足concept要求。

std::identity 不是“可有可无的语法糖”,它是 std::ranges 算法中唯一满足标准约束、支持 constexpr 且被深度优化的恒等投影类型;用 lambda 替代看似能跑通,但会在 concept 检查、零开销内联、模板推导等关键环节失败。
std::identity{} 必须带花括号初始化,否则编译失败
它是个空结构体,只支持聚合初始化:std::identity{} 是合法值;std::identity() 尝试调用不存在的默认构造函数,报 “no matching constructor”;std::identity(无括号)是类型名,传给算法参数时无法匹配 indirectly_unary_invocable 约束,SFINAE 直接丢弃该重载。
常见错误写法:
-
std::ranges::sort(v, std::identity)→ 类型不匹配,推导失败 -
std::ranges::sort(v, std::identity())→ 构造错误 -
std::ranges::transform(src, dst.begin(), [](auto&& x) { return x; })→ 满足调用,但不满足regular_invocable,在 requires clause 中直接拒斥
std::identity{} 在 ranges 算法中必须显式传参的典型场景
不是所有地方都要写,但以下情况漏掉就会出问题:
- 与自定义比较器共用,且需明确声明“不投影”:例如
std::ranges::sort(v, std::ranges::greater{}, std::identity{})—— 若省略第三个参数,部分实现可能退化为隐式投影,但std::ranges::transform等算法根本没有默认投影参数,不传就编译不过 - 泛型封装时占位:比如模板函数
template<typename r typename proj="std::identity"> void sort_by(R&& r, Proj proj = {})</typename>,调用sort_by(v, std::identity{})才能确保Proj被实例化为具体类型,避免 concept 检查被绕过 - 做无变换复制语义:用
std::ranges::transform(src, dst.begin(), std::identity{})比std::ranges::copy更统一——当逻辑可能切换为&T::field或 lambda 时,接口无需改形参数量
std::identity{} 和成员指针投影(如 &T::field)的协作关系
它们同属投影(proj)参数位置,但语义不同:std::identity{} 表示“用整个元素本身”,而 &T::field 表示“提取某个成员”。两者互斥,不能混用,但可以按需切换:
- 排序
vector<pair string>></pair>时,想按整个 pair 的默认序:必须用std::identity{},写成std::ranges::sort(v, {}, std::identity{});若漏掉,可能误触发pair成员的隐式投影(取决于实现),或因参数数不足编译失败 - 同一容器后续想切到按
.second排:只需把std::identity{}换成&pair<int>::second</int>,其余不变,接口稳定 - 注意:成员指针投影自动通过
std::invoke调用,std::identity{}则是直接返回参数,二者底层机制不同,但对用户透明
最容易被忽略的是:即使你没写 std::identity{},很多 std::ranges 算法(如 std::ranges::sort)内部默认投影就是它;但一旦你需要显式控制参数顺序、参与模板推导或满足 concept,那个小小的 {} 就不再是装饰,而是编译能否通过的分水岭。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











