C++ decay/decltype/declval

一、decay

在 C++ 14 后,可使用别名 std::decay_t

在 C++ 中,Decay(退化)是指将一种类型转换为另一种更简单、更“原始”的类型的过程。这通常发生在将表达式传递给函数参数或进行模板推导时。

在 C++11 中,标准库提供了 std::decay(定义在 `` 头文件中) ,它精确地实现了上述规则。它是模板元编程中非常重要的工具。

Decay-copy(“衰变复制”或“退化复制”)是 C++ 标准库中一个非常重要的概念,它通常在涉及泛型编程和模板时发挥作用,尤其是在处理右值引用和转发引用时。

这个概念的核心在于 std::decay 类型特性和完美转发的应用。

std::decay<T>::type 的作用是模拟函数按值传递时参数类型的“衰变”(类型转换)。

  • 将数组类型(如 int[5])衰变成指向其元素类型的指针类型(如 int*
  • 去除顶层的 constvolatile 修饰符(cv 限定符)
  • 将函数类型(如 void(int))衰变成函数指针类型(如 void(*)(int)
  • 去除引用(&&&

这套转换规则正是 C++ 函数按值传参时编译器自动执行的类型调整。std::decay把它抽象成一个模板元函数,让我们能在编译期获取“退化后”的类型。

Decay-copy 本质上是使用 std::decay 处理后的类型进行复制。在 C++11/14/17 中,标准库的一些组件(如 std::bindstd::thread::thread 构造函数)在内部会使用 std::decay_t(C++14/17 别名)或类似机制来保存(复制)它们所捕获或接收的参数。

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <type_traits>
#include <typeinfo>

template <typename T>
void checkType() {
using DecayedType = typename std::decay<T>::type;
std::cout << "Original: " << typeid(T).name()
<< " -> Decayed: " << typeid(DecayedType).name() << std::endl;
}

int main() {
checkType<const int&>(); // -> int
checkType<int[5]>(); // -> int*
checkType<int(int)>(); // -> int (*)(int)
checkType<volatile int&&>(); // -> int
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
struct Counter
{
void increase()
{
cout << "increase" << endl;
}
};

template <typename T>
void int_counter(T &intTypeCounter, std::decay_t<decltype(++intTypeCounter)> * = nullptr)
{
++intTypeCounter;
cout << "num: " << intTypeCounter << endl;
}

template <typename T>
void int_counter(T &counterObj, std::decay_t<decltype(counterObj.increase())> * = nullptr)
{
counterObj.increase();
}

void test()
{
Counter cntObj;
uint32_t cntUI32 = 1;

int_counter(cntObj);
int_counter(cntUI32);
}

int main()
{
test();

return 0;
}

二、decltype

C++11新特性(一)

三、declval

在模板元编程中,std::declval 是一个“虚构对象”,它定义在 <utility> 头文件中。

它的核心作用是:在编译期,欺骗编译器,让它认为你已经拥有了一个类型为 T 的对象。用于在不实际调用构造函数的情况下生成一个对象引用,以便在类型推导中使用。通常在需要引用某个类型的对象但实际无法创建该对象的上下文中使用,例如在函数返回类型推导中:

1
2
3
4
template <typename T>
auto createAndProcess() -> decltype(std::declval<T>().process()) {
// 此处只是用于类型推导,并不会实际创建 T 的对象
}

在模板中,如果你想获取某个表达式(例如调用某个成员函数)后的返回类型,你通常需要写出: decltype(obj.method())

但如果 obj 的类型 T

  • 没有默认构造函数(必须传递参数才能创建)
  • 构造函数极其复杂(你想在编译期检查,而不是运行期构造)
  • 构造函数是私有的

这时候你无法直接创建 T 的实例,std::declval 完美解决了这个问题。

在这个例子中,std::declval().process() 用于推导函数的返回类型,但并不会实际调用 process 函数。

实例:推导一个复杂的返回类型

假设我们有一个类 Widget,它没有默认构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <utility>
#include <iostream>

struct Widget {
Widget(int, double) {} // 无默认构造函数
int compute(int x) { return x * 2; }
};

// 如果没有 declval,我们很难写出 compute 的返回类型推导
using ReturnType = decltype(std::declval<Widget>().compute(0));

int main() {
// 验证推导结果
static_assert(std::is_same_v<ReturnType, int>);
std::cout << "推导成功!" << std::endl;
}

注意:

  1. 绝对不要在运行时调用:std::declval() 仅存在于编译器眼中。如果在你的普通函数体内调用它,会导致编译链接失败
  2. 左值 vs 右值:
    • std::declval():返回 T&&(右值)。
    • std::declval():返回 T&(左值)。
    • 这对于重载函数的分辨非常重要(例如判断一个类是否支持 push_back(T&&)push_back(const T&))。

C++ decay/decltype/declval
http://example.com/2026/05/17/C++-decay-decltype-declval/
作者
Yu xin
发布于
2026年5月17日
许可协议