C++identity透传参数函数对象
C++ 里那个“啥也不干却不可或缺”的函数对象:identity 透传参数的实战意义
你写过 std::transform,也用过 std::sort 的自定义比较器,但有没有哪次调试时突然卡住——明明 lambda 写得清清楚楚,可编译器非说“无法推导模板参数”?或者在写通用容器适配器时,发现少了一个“原样转发”的兜底选项,结果硬生生多套了一层包装?
这时候,std::identity 就不是教科书里的装饰品了,它是 C++20 给你悄悄塞进标准库的一把小镊子:不改变任何东西,却让类型流完整穿过。
std::identity 是一个函数对象(function object),它的 operator() 接收一个参数,并原封不动地返回它。看起来像这样:
struct identity {
template<class T>
constexpr T&& operator()(T&& t) const noexcept {
return static_cast<T&&>(t);
}
};
别被这个签名吓住。它不是为了炫技,而是为了解决一个真实痛点:当泛型代码需要“保持原语义不变”地透传参数时,你不能随便写 [](auto&& x) { return x; } ——那会丢失引用类别、cv 限定符,甚至破坏 move 语义。
举个具体例子。假设你在封装一个支持投影(projection)的查找函数:
template<class Iter, class T, class Proj>
Iter find_if_proj(Iter first, Iter last, const T& value, Proj proj) {
for (; first != last; ++first)
if (proj(*first) == value) return first;
return last;
}
调用时,你想查某个 vector 里值本身等于 42 的元素,最自然的写法是:
std::vector<int> v = {1, 42, 3};
auto it = find_if_proj(v.begin(), v.end(), 42, std::identity{});
注意:这里传的是 std::identity{},不是 [](auto&& x){return x;}。为什么?
因为 std::identity 的 operator() 是 完美转发感知的:它保留左值/右值属性、const/volatile 限定,且 noexcept。而手写的 lambda 即使加了 auto&&,其返回类型推导仍可能退化为值类型(尤其在旧标准或某些编译器下),导致对 std::string_view 这类轻量视图的意外拷贝,或对 std::unique_ptr 的误移动。
更隐蔽的问题出在 SFINAE 和概念约束中。比如你写了个 requires 子句:
template<class Proj>
requires std::regular_invocable<Proj&, int&>
void process(int& x, Proj&& p) { /* ... */ }
传入 std::identity{} 能稳稳通过;但传入一个普通 lambda,编译器可能因重载解析歧义或模板参数推导失败而拒之门外——它不满足 regular_invocable 对 noexcept 和可复制性的隐含要求。
所以,std::identity 不是“语法糖”,它是类型系统里一块精确校准的垫片:尺寸固定、材质可靠、不引入额外行为。
实际项目中,它常出现在三类场景:
- 算法投影兜底:
std::ranges::sort(v, {}, std::identity{})明确告诉读者“这次真没做变换”; - 策略模式默认实现:类模板带
Projection = std::identity默认模板参数,使用者不传就走透传路径,无需特化或偏特化; - 元编程桥接:配合
std::bind_front或std::not_fn使用时,std::identity是唯一能安全插入“空操作”位置的标准化组件——它不污染is_invocable_v,不干扰invoke_result_t推导。
顺带一提,std::identity 在 C++20 中才正式进入 <functional>,但它的思想早有实践。有人用 [](auto&& x) -> auto&& { return x; } 模仿,但这种写法在模板上下文中极易触发 ODR-use 问题或链接错误;也有人自己写 struct identity,但容易漏掉 noexcept、constexpr 或完美转发细节。标准版的优势在于:它被所有主流 STL 实现严格测试过,与 std::ranges、std::expected 等新设施协同无坑。
最后说个容易被忽略的细节:std::identity 是可默认构造、可复制、可比较(总是相等)的。这意味着你可以把它存进 std::function<void()> 吗?不能。std::function 要求可调用对象满足 CopyConstructible 且 Callable,而 std::identity 的 operator() 是模板,std::function<void()> 无法绑定到未实例化的模板成员上。所以它只适合直接传给接受模板参数的函数(如 std::ranges 算法),而非运行时多态容器。
总结一句实在话:
当你在泛型代码里需要“不做任何事,但必须做对”时,std::identity 就是那个不抢戏、不出错、不拖慢编译速度的配角——它存在的全部意义,就是让你的主逻辑更干净、更可读、更少 bug。
下次写 projection 接口,别再犹豫要不要手写一个“看起来一样”的 lambda。标准库已经替你验好了这块砖。


还没有评论,来说两句吧...