结论:类型trait是编译期工具,应使用_v变量模板(如std::is_integral_v<T>)而非::value;remove_reference_t等保持恒等映射;decay_t还处理数组/函数衰变;enable_if_t用于SFINAE而非运行时判断;所有trait均不支持不完整类型。

直接说结论:别用 value 成员做运行时判断,也别在非模板上下文中硬套 std::is_integral 这类 trait —— 它们是编译期工具,不是运行时类型反射。
怎么判断一个类型是不是整数?用 std::is_integral_v,不是 std::is_integral<T>::value
从 C++17 起,所有标准 is_* 类型谓词都提供了对应的变量模板(_v 后缀),比如 std::is_integral_v<int>。它等价于 std::is_integral<int>::value,但更简洁、可读性更高,且避免了模板参数推导失败时的冗长错误信息。
- 写
static_assert(std::is_integral_v<T>, "T must be integral");,别写static_assert(std::is_integral<T>::value) - 在
if constexpr中必须用_v形式:if constexpr (std::is_floating_point_v<T>) { ... } - 旧写法
::value在模板嵌套较深时容易触发 SFINAE 失败,而_v是 constexpr 值,更“钝感”
为什么 std::remove_reference_t<int&&> 得到 int,但 std::remove_reference_t<int> 也是 int?
std::remove_reference 的作用不是“只去掉引用”,而是“如果 T 是引用类型,就去掉;否则保持原样”。它不报错、不警告、也不做隐式转换 —— 这正是类型转换 trait 的设计哲学:安全、无副作用、恒等映射(identity)对非目标类型成立。
-
std::remove_reference_t<int&>→int -
std::remove_reference_t<int&&>→int -
std::remove_reference_t<int>→int(没引用可去,就原封不动) - 类似行为也适用于
std::remove_const_t、std::remove_pointer_t等
std::decay_t 不只是去 const 和引用,它还干了三件事
std::decay 模拟的是函数传参时的“类型衰变”规则,常被误认为只是“去顶层 const 和引用”,其实它还处理数组和函数类型:
立即学习“C++免费学习笔记(深入)”;
- 数组类型(如
int[5])→ 指针类型(int*) - 函数类型(如
void())→ 函数指针类型(void(*)()) - 带 const/volatile/引用的类型 → 去除顶层 cv 和引用,再加 const(例如
const int&→const int) - 所以
std::decay_t<int[3]>是int*,不是int[3];std::decay_t<void()>是void(*)()
别把 std::enable_if_t 当开关,它本质是“让模板失效”
std::enable_if 不是用来控制代码分支的,它的唯一作用是参与 SFINAE:当条件为 false 时,让某个模板特化或重载“根本不存在”,从而让编译器去选别的候选。一旦你把它用在非模板上下文,或者忘了加 typename / _t 后缀,就会立刻报错。
- 正确姿势(函数模板重载):
template<typename T> auto foo(T t) -> std::enable_if_t<std::is_integral_v<T>, int> { ... } - 错误姿势:
if (std::enable_if_v<std::is_integral<T>>) { ... }——enable_if没有_v,它不产出布尔值 - 更隐蔽的坑:
using type = std::enable_if<Cond, T>::type;在 C++14+ 应写作using type = std::enable_if_t<Cond, T>;
最易被忽略的一点:所有 type_traits 工具都不处理不完整类型(incomplete types)。比如在类定义体内使用 std::is_base_of<Base, Derived>,而 Derived 尚未定义完成,结果是未定义行为 —— 编译器可能静默接受,也可能突然报错,且不同标准库实现行为不一致。


















