有,而且用得很多。C++14 引入了返回类型推导(auto f() { ... }),以前 auto f() -> decltype(expr) 这种尾置返回类型大多可以不写了,所以容易让人觉得 decltype 没用了。实际上,auto 只能"推导一个值的类型",decltype 能"精确查询一个表达式的类型",两者不能互相替代。

根本区别:auto 会退化,decltype 不会

int x = 0;  int& rx = x;  const int cx = 0;

auto a = rx;            // int         —— auto 丢掉引用和顶层 const
auto b = cx;            // int

decltype(x)             // int         —— 名字:取声明时的类型
decltype(rx)            // int&
decltype(cx)            // const int
decltype((x))           // int&        —— 加括号变成表达式:左值 → T&
decltype(x + 1)         // int         —— 纯右值 → T
decltype(std::move(x))  // int&&       —— 将亡值 → T&&

它的规则只有两条:

  • 如果是不带括号的名字,结果是这个名字声明时的类型。
  • 其他情况按值类别决定:左值得到 T&,将亡值得到 T&&,纯右值得到 T

C++14 之后仍然离不开 decltype 的场景

场景 为什么 auto 不行 写法
泛型 lambda 里完美转发 auto&& 参数没有模板参数名可用 [](auto&& x) { f(std::forward<decltype(x)>(x)); }
原样返回引用 auto 返回类型会退化成值 decltype(auto) get(C& c, int i) { return c[i]; },返回 T&
表达式 SFINAE 推导出的返回类型不参与 SFINAE,函数体里的错误是硬错误 auto f(T t) -> decltype(t.foo(), void())std::void_t<decltype(...)> 检测惯用法
拿一个类型去当模板实参或成员类型 非静态数据成员不能写 auto,模板实参也不能写 auto std::unique_ptr<FILE, decltype(&fclose)>std::set<int, decltype(cmp)> s(cmp);
没有对象时计算类型 auto 需要一个真实的初始化表达式 decltype(std::declval<T>().size())std::declval 只能用在这种不求值的上下文里
实现类型萃取 同上 std::common_type 就是基于 decltype(false ? declval<T>() : declval<U>()) 实现的
只有声明、定义在别的编译单元 auto f(); 在定义可见之前不能调用 头文件里写 auto f(T) -> decltype(...);,或者直接写出返回类型
用 lambda 的类型 lambda 的类型无法写出名字 C++20 起无状态 lambda 可以默认构造:std::set<int, decltype([](int a, int b) { return a > b; })>

标准库里的 std::nullptr_t 本身就定义为 decltype(nullptr)

decltype(auto) 本身就是 decltype

C++14 新增的 decltype(auto) 意思是"用 decltype 的规则来推导",常用来原样转发返回值:

template <class F, class... A>
decltype(auto) call(F&& f, A&&... a) {
    return std::forward<F>(f)(std::forward<A>(a)...);   // f 返回引用就返回引用,返回值就返回值
}

有一个经典的坑:return (x); 会被推导成 T&,如果 x 是局部变量,就返回了悬空引用。

decltype(auto) bad() { int x = 0; return (x); }   // 返回 int&,悬空

C++20 之后的变化

  • concepts 和 requires 取代了大部分 SFINAE 写法requires { t.foo(); } 不再需要 decltype(..., void()) 这种技巧。
  • 模板 lambda 可以取代泛型 lambda 里的 decltype(x)[]<class T>(T&& x) { f(std::forward<T>(x)); }

但以下几个场景仍然需要 decltype:

  • 查询表达式的精确类型
  • decltype(auto)
  • 拿 lambda 或函数指针的类型去做模板实参
  • 写类型萃取

面试时可以这样回答:C++14 的返回类型推导只取代了"尾置 -> decltype(expr) 用来写返回类型"这一种用法。decltype 的本职是不求值地、保留引用和 cv 限定地查询表达式类型,这是 auto 做不到的。