C++中如何利用std::identity在泛型算法中保持默认投影?(函数式模板)

2次阅读

C++中如何利用std::identity在泛型算法中保持默认投影?(函数式模板)

std::identity 是什么,为什么它能当默认投影?

它就是个最简恒等函数对象std::identityoperator() 直接返回传入参数,不做任何转换。泛型算法(比如 std::ranges::sortstd::ranges::max_element)接受投影参数时,需要一个可调用对象把每个元素映射成“用于比较的值”。不提供投影时,算法默认按元素本身比较;提供 std::identity{},效果完全一致——但显式写出它,能让模板参数推导更稳定,尤其在约束模板或 SFINAE 场景下。

常见错误现象:std::ranges::sort(v, std::less{}, std::identity{}) 编译失败,因为部分标准库实现(如早期 libstdc++)未完全支持 std::identity 作为投影(c++20 要求支持,但实现在 C++20 初期有差异)。

  • 必须确认编译器和标准库版本支持 C++20 完整特性(GCC ≥10.2 / Clang ≥12 / MSVC ≥19.28)
  • 头文件只需 <functional></functional>,无需额外包含
  • 不能写成 std::identity()(这是类型名),必须是 std::identity{}std::identity()(注意括号是构造,不是函数调用)

在 ranges 算法中怎么安全传 std::identity

直接传 std::identity{} 是标准写法,但要注意参数顺序和重载解析。例如 std::ranges::sort 的签名是 (range, comp = less{}, proj = identity{}),所以投影是第三个参数;而 std::ranges::max_element 投影是第四个参数(前三个是 range、comp、proj)。

使用场景:当你写一个通用函数模板,想让调用者既能传自定义投影(如 &Person::age),又能明确表示“就用原值”,这时 std::identity{} 比裸写 std::less{} 更语义清晰,也避免误传比较器当投影。

立即学习C++免费学习笔记(深入)”;

  • 不要省略花括号:std::identity 是类型,std::identity{} 才是实例
  • 避免和 std::refLambda 混用:比如 [&](auto&& x) { return x; } 行为类似,但类型不同,可能破坏概念约束(如 std::invocable 推导失败)
  • 性能无开销:编译器几乎总能内联掉 std::identity::operator()

替代方案有哪些?什么时候不该用 std::identity

最常见替代是省略投影参数(靠默认值),或者用 lambda [](auto&& x) { return x; }。但前者在模板中可能引发推导歧义(比如你模板里只写 sort(r, comp),没提投影,编译器无法知道你是想用默认投影还是漏写了);后者每次生成新类型,影响编译时间和缓存一致性。

容易踩的坑:std::identity 不接受 cv 限定或引用修饰——它对 const int&int&& 都返回原类型,但如果你投影目标是 const T&,而容器是 vector<t></t>,则 std::identity{} 返回的是 T&&T&,可能触发移动或绑定问题。

  • 对只读访问场景(如 std::ranges::find_if),std::identity{} 安全
  • 若算法内部会修改投影结果(极少见),别用 std::identity,改用显式转型 lambda
  • 跨平台 CI 中,建议加静态断言:static_assert(std::is_invocable_v<:identity int>);</:identity>

std::identity 在函数式模板中的真实作用点

它的核心价值不在“做了什么”,而在“声明了什么”:告诉模板系统“此处投影存在,且是恒等映射”,从而激活某些 requires 子句、启用特定重载、或满足 concept 约束(如 std::projected<i proj></i>)。没有它,有些泛型代码在推导 std::projected 类型时会失败,不是因为逻辑错,而是编译器找不到匹配的 Proj 实例。

示例:写一个通用查找最大值并返回投影后值的函数,签名含 template <class r class proj="std::identity"></class>,此时 Proj{} 必须可默认构造——std::identity 满足,而 [](auto x) { return x; } 不满足。

  • 函数模板中设默认模板参数为 std::identity,比设为 void + 特化更干净
  • 注意:C++20 前没有 std::identity,需手写等效结构体,但命名和语义要保持一致
  • 它不解决运行时问题,只解决编译期建模——这点最容易被忽略
text=ZqhQzanResources