std::type_identity 是 c++20 引入的类型工具,用于阻断模板参数自动推导,确保调用者显式指定类型并保留 cv 限定符和引用限定符;其别名 std::type_identity_t 须用于函数参数类型,不可直接使用模板名。

std::type_identity 是什么,为什么需要它
当你写模板函数时,编译器会尝试从实参自动推导模板参数类型。但有时你**不希望推导发生**——比如想强制要求调用者显式指定类型,或防止 cv 修饰符(const、&、&&)被悄悄剥离。这时 std::type_identity 就是标准库提供的“类型防火墙”:它把一个类型 T 包装成 std::type_identity_t<t></t>,而这个别名在推导时**不参与类型匹配**,从而阻断推导。
std::type_identity 阻断模板参数推导的典型写法
常见错误是直接把 std::type_identity<t></t> 当作函数参数类型——这不行,因为 std::type_identity<t></t> 是个类模板,不能直接用;必须用其公开的 type 成员别名,即 std::type_identity_t<t></t>。
- 正确写法:
template<typename t> void foo(std::type_identity_t<t> x);</t></typename>
- 错误写法:
template<typename t> void foo(std::type_identity<t> x); // 编译失败:std::type_identity<t> 不是类型,而是模板</t></t></typename>
- 如果 T 是
int&,std::type_identity_t<int></int>就是int&,但推导时不会把int&反向解包为T = int—— 它卡死在“必须由调用者提供 T”这一步
对比:不用 type_identity 时会发生什么
看这个例子:
template<typename t>
void bar(T x) { static_assert(std::is_same_v<t int>); }</t></typename>
调用 bar(42) 会推导出 T = int,没问题;但 bar(const int{42}) 也会推导出 T = int(const 被忽略),bar(std::move(x)) 推导出 T = int(右值引用也被折叠)。你失去了对 cv/ref 限定的控制。
加上 std::type_identity_t 后:
template<typename t>
void bar(std::type_identity_t<t> x) { /* ... */ }</t></typename>
-
bar<int>(42)</int>✅ 显式指定T = int -
bar<const int>(42)</const>✅ 允许const int作为 T -
bar<int>(x)</int>✅ 保留左值引用语义 -
bar<int>(x)</int>❌ 即使 x 是int&,也不会自动推导成T = int&—— 推导失败,强制显式传参
实际使用中容易踩的坑
std::type_identity_t 在 C++20 引入,低于 C++20 的标准库不提供。如果你用的是 GCC 10 或 Clang 11 之前的版本,可能得自己实现:
template<typename t>
struct type_identity { using type = T; };
template<typename t>
using type_identity_t = typename type_identity<t>::type;</t></typename></typename>
另外注意:它只影响**模板参数推导**,不影响重载决议或 SFINAE;它也不能阻止用户传入不兼容类型的实参——只是让推导失效,最终仍靠编译器报错来约束。
最常被忽略的一点:它和 std::declval、std::remove_reference 等工具目的不同——std::type_identity_t 不做类型变换,只做“推导隔离”。用错场景(比如想删掉 & 却用了它)会导致编译失败而不是预期效果。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











