17370845950

c++中如何使用decltype获取表达式类型_c++11 decltype用法
decltype原封不动还原表达式类型,含引用/const/volatile;单变量名推声明类型,括号变量推值类别类型,函数调用推返回类型,运算推计算类型。

decltype 用来推导表达式类型,不是变量声明类型

很多人误以为 decltypeauto 一样是“自动推类型”,但它的核心是:**原封不动地还原表达式的类型(包括引用、const、volatile)**。比如 int x = 0;decltype(x)int,但 decltype((x))int& —— 因为加了括号后变成左值表达式。

  • 单个未加括号的变量名 → 推出该变量的声明类型(去掉引用/const修饰的“裸类型”)
  • 带括号的变量(如 (x))→ 推出该表达式的值类别对应类型(左值→T&,右值→T&&
  • 函数调用表达式(如 foo())→ 推出其返回类型(含引用限定符)
  • 内置运算(如 a + b)→ 推出按 C++ 表达式规则计算出的类型(比如 int + longlong

常见误用:decltype(x) 和 decltype((x)) 完全不同

这是最易踩坑的地方。C++11 标准明确区分了“标识符”和“表达式”两种情形:

  • decltype(x):x 是一个“未加括号的标识符”,直接取其声明类型
  • decltype((x)):x 被括号包裹,成为左值表达式,结果是 int&(假设 x 是 int
int x = 42;
decltype(x) y1 = x;     // y1 类型是 int
decltype((x)) y2 = x;   // y2 类型是 int&,必须绑定到左值(如 y2 = 100 是合法的)
// decltype((x)) y3;    // 错误:不能声明 int& 类型的未初始化变量

配合 auto 和模板写泛型代码时,decltype 常用于尾置返回类型

当返回类型依赖于参数表达式(比如两个迭代器相减、两个容器元素相加),无法在函数名前写出类型时,decltype 就成了必需工具。

  • 必须搭配 auto 函数声明 + 尾置返回类型语法(-> decltype(...)
  • 表达式中不能出现尚未声明的形参名(所以参数要先写在函数头)
  • 若表达式含副作用(如 i++),decltype 不执行它,只做类型分析
template
auto add_derefs(It a, It b) -> decltype(*a + *b) {
    return *a + *b;
}

decltype 在 typedef / using 别名和模板元编程中很实用

比起硬写冗长类型(如 std::vector<:string>::iterator),用 decltype 可从已有对象或表达式中“抓取”类型,更安全也更易维护。

  • 适用于复杂嵌套类型、lambda 类型(lambda 没有可写的名字)、临时对象返回类型
  • 注意:lambda 表达式本身是右值,decltype({}) 推出的是闭包类型;而 decltype(({})) 会编译失败(因为 {} 不是表达式)
  • 在 SFINAE 场景中,常与 std::declval 配合构*想表达式(如 decltype(std::declval().size())

实际写别名时,优先用 using

std::vector v;
using iter_t = decltype(v.begin());  // 直接从实例推,比手写 std::vector::iterator 更稳
C++11 的 decltype 看似简单,但括号是否加、表达式是否求值、左值/右值语义是否被保留——这些细节一旦忽略,就会导致类型不匹配、引用绑定失败或模板推导崩溃。尤其在写通用容器适配器或表达式模板时,多花两秒看一眼 decltype((x))decltype(x) 的差异,能省掉半天调试时间。