std::type_identity 是c++20引入的别名模板,用于阻止模板参数自动推导以实现类型“不透明”;典型用途是在函数模板中强制显式指定类型,避免重载错误或sfinae失败,如 make() 中通过 std::type_identity_t* 参数冻结t的推导。

std::type_identity 是什么,为什么模板里需要它
它是 C++20 引入的一个别名模板,定义为 template<class t> using type_identity = T;</class>。表面看啥也没做,但关键在于:它能阻止编译器对模板参数做自动推导——也就是让类型“不透明”一次。
典型场景是函数模板重载或调用时,你希望某个参数**必须显式指定类型**,而不是被实参悄悄推导走,否则可能选错重载、触发 SFINAE 失败,或者让 auto 推导出意外的引用/const 修饰。
什么时候必须用 std::type_identity_t 替代原类型
常见于函数模板参数中,你想“冻结”某个类型,不让它参与推导。比如写一个通用工厂函数,想强制用户传入目标类型,而不是靠返回值或参数反推:
- 错误写法:
template<typename t> T make();</typename>—— 调用make()时T根本无法推导,编译失败 - 半正确写法:
template<typename t> T make(T*);</typename>—— 可推导,但要求你先构造个T*,违背初衷 - 正确写法:
template<typename t> T make(std::type_identity_t<t>* = nullptr);</t></typename>——std::type_identity_t<t></t>不参与推导,T必须显式写出,如make<int>()</int>
注意:必须用 std::type_identity_t<t></t>(即 using 别名展开后的形式),不能直接写 std::type_identity<t></t>,后者是个类模板,不是类型。
立即学习“C++免费学习笔记(深入)”;
容易踩的坑:和 std::decay_t / std::remove_reference_t 混用
std::type_identity_t 不做任何类型转换,这点和 std::decay_t 或 std::remove_reference_t 完全不同。有人误以为它能“擦除引用”,结果发现 std::type_identity_t<int></int> 还是 ,导致转发失败或绑定错误。
- 想保留原始类型(含 const/volatile/引用)→ 用
std::type_identity_t - 想模拟函数参数传递后的类型(去引用、去 const、数组转指针)→ 用
std::decay_t - 只去引用,其他不动 → 用
std::remove_reference_t
混用会导致模板实例化出完全不同的签名,尤其在完美转发或 trait 检查中,可能让 std::is_same_v 突然返回 false。
兼容性与替代方案(C++17 及更早)
如果你还在用 C++17,std::type_identity 不存在,但可以用等效写法模拟:
template<class T> struct type_identity { using type = T; };
template<class T> using type_identity_t = typename type_identity<T>::type;注意:自定义版本没有标准库的 SFINAE 友好性保证,不过对绝大多数场景已足够。另外,某些老编译器(如 GCC 8.4 之前)对别名模板的推导抑制支持不完整,建议测试 make<int>()</int> 是否真不报错。
真正难处理的其实是嵌套模板参数里的推导冲突——比如 std::vector<:type_identity_t>></:type_identity_t> 本身不会阻止 T 推导,只有出现在函数参数位置才生效。这点很容易被忽略,以为包一层就万事大吉。










