std::type_identity 是 C++20 引入的工具模板,通过将 T 置于非推导上下文(如 typename std::type_identity<T>::type)阻止模板参数自动推导,确保类型由显式指定或间接推导决定。
std::type_identity 是 C++20 引入的工具类型别名模板,定义为:
template<class T> struct type_identity { using type = T; };它本身不改变类型,但关键在于:当用作函数参数类型时,T 在 type_identity<T>::type 中属于「非推导上下文」——编译器不会从实参反向推导 T,必须显式指定或通过其他参数间接确定。这和直接写 T 形成鲜明对比:比如 void f(T x) 会尝试从 x 推导 T;而 void f(typename std::type_identity<T>::type x) 则禁止该推导。
典型场景是希望某个参数类型由调用者明确控制,而非被实参“带偏”。比如设计一个通用 setter,要求传入的值类型必须严格匹配成员类型,不能因隐式转换或 cv 修饰差异导致意外推导:
template<typename T>void set_value(typename std::type_identity<T>::type val) { // val 类型必须是 T,不会因传入 int 而推导出 T=int,再接受 long 自动转}
typename std::type_identity<T>::type,不能省略 typename(因为 ::type 是依赖名称)std::type_identity<T> 本身——那是个类型,不是 T,传参会失败type_identity,其余保持可推导最常遇到的错误是编译器报 couldn't deduce template argument for 'T',但这恰恰说明你成功阻止了推导——问题不在 type_identity,而在调用时没提供 T 的显式信息。
立即学习“C++免费学习笔记(深入)”;
set_value(42); → 编译失败,因为 T 完全无法推导set_value<int>(42); 或 set_value<long>(42L);
template<typename T>void set_and_log(typename std::type_identity<T>::type val, const std::string& msg);此时若写
set_and_log(42, "hi"),T 仍无法推导,必须写 set_and_log<int>(42, "hi")
有人试图用 const T& 或自定义包装类来“冻结”类型,但效果不同:
const T& 仍允许推导(如传 int 可推 T=int),且会接受临时对象,无法防止 cv 退化struct hold{T t;})可行但笨重,还需额外构造/解包std::type_identity 是标准、轻量、语义清晰的解决方案,专为此场景设计template<class T> using type_identity = T; 无效,必须引入非推导上下文,例如 struct identity { using type = T; };)真正容易被忽略的是:它只作用于模板参数声明位置,且仅影响该参数的推导行为;一旦 T 被其他方式确定(比如默认模板参数、或另一参数推导出),type_identity 就只是个透明壳子。