C++ 从入门到精通之十九

大纲

C++ 进阶

decltype 类型推导说明符

核心概念

decltype 是 C++ 11 引入的类型推导说明符,用于在编译期获取一个表达式或实体的精确类型,而无需实际求值该表达式。它与 auto 的核心区别在于:auto 基于赋值忽略引用和顶层 const,而 decltype 会忠实保留表达式的值类别与 cv 限定符 —— 对变量名使用得到其声明类型,对左值表达式得到引用类型,对纯右值得到非引用类型。这一特性使其成为泛型编程中编写完美转发、尾置返回类型以及依赖表达式类型的模板代码时不可或缺的工具。值得一提的是,decltype 的类型推断发生在编译期,并且它只分析表达式的类型而不对其求值,因此不会真正计算表达式的值。

适用场景

  • decltype 的适用场景

    • 泛型编程中推导表达式类型:类型依赖模板参数、难以手写时,让编译器自动推导。
    • 尾置返回类型:函数返回类型依赖参数表达式时,用 decltype 描述返回值类型。
    • 完美转发:配合 std::forward 保留实参的值类别(左值 / 右值)。
    • 保留 cv 与引用限定符:需要原样保留 const / volatile / & / && 时,decltype 比 auto 的类型推导更精确。
    • 声明与某表达式同类型的变量:如 decltype(x) y = ...;,保证 y 与 x 类型完全一致。
    • 萃取函数返回类型:用 decltype(f()) 获取函数返回值类型,而不实际调用函数。
    • 配合 std::function / 模板元编程:用 decltype(f) 作为函数签名传入模板参数。
    • SFINAE 与类型检测:结合 decltype 检测某个表达式是否合法(如成员是否存在)。
  • decltype 的不适用场景

    • 需要实际求值:decltype 只做类型分析,不求值,无法获取表达式的运行结果。
    • 想要 auto 的退化行为:auto 会丢弃顶层 const 和引用,若想要这种退化,不该用 decltype。
    • 花括号初始化列表:decltype({1, 2, 3}) 无法推导,需改用 auto 或显式类型。
    • 希望得到函数指针:decltype(f) 得到函数类型而非指针,需改显式写 decltype(f)*。
    • 希望发生数组到指针退化:decltype(arr) 保留数组类型 int[3],不会退化为 int*。
    • 运行时类型判断:decltype 是编译期行为,无法处理运行时才确定的类型。
    • 对未声明实体使用:要求表达式中的名字必须可见、可解析,否则编译失败。
    • 作为类型简写:decltype 是说明符,不能替代 using / typedef 的命名作用。

推导规则

特别注意

以下案例代码都运行在 Windows 环境,并依赖了 Boost 库(主要是为了方便查看 auto 类型推导的结果,使用的 Boost 库版本是 1.92)。

作用于变量名
推导规则
  • decltype 作用于变量名(未加括号)的推导规则:

    • 对变量名使用 decltype 是特例,直接返回其声明类型,不加引用,完整保留 const / volatile / 引用。
    • 与 auto 的关键区别:
      • const int a = 10; auto x = a; 会丢弃顶层 const 或引用,推导为 int。
      • const int a = 10; decltype(a) d; 完整保留 const,得到 const int。
    • 括号陷阱:
      • decltype(x) → int(变量名特例)
      • decltype((x)) → int&(变量名加括号后会退化为左值表达式)
  • decltype 作用于变量名(未加括号)的常见案例:

    变量声明decltype 使用示例推导结果说明
    int x;decltype(x)int普通变量,返回声明类型本身
    const int a;decltype(a)const int保留顶层 const
    volatile int v;decltype(v)volatile int保留顶层 volatile
    const volatile int cv;decltype(cv)const volatile int同时保留 const 和 volatile
    int& r = x;decltype(r)int&保留引用,不退化
    const int& b = a;decltype(b)const int&同时保留引用和 const
    int&& rr = 0;decltype(rr)int&&保留右值引用
    int arr[3];decltype(arr)int[3]保留数组类型,不退化为指针
    int* p;decltype(p)int*指针类型本身
    MyClass obj;decltype(obj)MyClass类类型对象
推导案例
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <boost/type_index.hpp>
#include <iostream>

// 自定义类
class MyClass {
public:
float i;
double j;
};

// 辅助打印推导类型(保留 const/volatile/引用 属性)
template <typename T>
void print_type(const char* name) {
std::cout << name << " = " << boost::typeindex::type_id_with_cvr<T>().pretty_name() << std::endl;
}

int main() {
int x = 0;
int y = 0;
const int a = 1;
const int& b = a;
MyClass my_class;

// 案例 1:auto 值传递,顶层 const 和引用均被丢弃
auto c = a; // auto = int,c 类型为 int
print_type<decltype(c)>("decltype(c)");

// 案例 2:decltype 作用于变量名
decltype(a) d = 15; // decltype(a) = const int,d 类型为 const int
print_type<decltype(d)>("decltype(d)");

// 案例 3:decltype 作用于引用变量名
decltype(b) e = d; // decltype(b) = const int&,e 类型为 const int&
print_type<decltype(e)>("decltype(e)");

// 案例 4:decltype 作用于类类型对象
decltype(my_class) f; // decltype(my_class) = MyClass,f 类型为 MyClass
print_type<decltype(f)>("decltype(f)");

// 案例 5:decltype 作用于类数据成员
decltype(MyClass::i) g = 5.0; // decltype(MyClass::i) = float,g 类型为 float
print_type<decltype(g)>("decltype(g)");

// 案例 6:auto&& 作为万能引用绑定左值
auto&& h = x; // auto = int&,int& && 经引用折叠变为 int&,h 类型为 int&
print_type<decltype(h)>("decltype(h)");

// 案例 7
decltype(h)&& i = y; // decltype(h) = int&,int& && 经引用折叠变为 int&,i 类型为 int&
print_type<decltype(i)>("decltype(i)");

// 案例 8:auto 推导数组——数组退化为指针
int arr[3] = {1, 2, 3};
auto k = arr; // arr 退化为指向首元素的指针,auto = int*,k 类型为 int*
print_type<decltype(k)>("decltype(k)");

// 案例 9:decltype 作用于数组名——保留完整数组类型,不退化为指针
decltype(arr) l; // decltype(arr) = int[3],l 类型为 int[3]
print_type<decltype(l)>("decltype(l)");

// 案例 10:decltype 作用于数组下标表达式——返回左值,得到元素引用类型
decltype(arr[0]) m = x; // decltype(arr[0]) = int&,m 类型为 int&
print_type<decltype(m)>("decltype(m)");

// 案例 11:decltype 作用于数组名加括号——退化为左值表达式,得到数组引用类型
decltype((arr)) n = arr; // decltype((arr)) = int(&)[3],n 类型为 int(&)[3]
print_type<decltype(n)>("decltype(n)");

return 0;
}

程序运行的结果如下:

1
2
3
4
5
6
7
8
9
10
11
decltype(c) = int
decltype(d) = int const
decltype(e) = int const&
decltype(f) = MyClass
decltype(g) = float
decltype(h) = int&
decltype(i) = int&
decltype(k) = int*
decltype(l) = int [3]
decltype(m) = int&
decltype(n) = int (&) [3]
作用于表达式
推导规则
  • decltype 作用于表达式的推导规则:

    • decltype 的推导规则按表达式值类别分三种情况:
      • (1) 若表达式是纯右值(prvalue),返回其类型本身(非引用);
      • (2) 若表达式是左值(lvalue),返回 T&;
      • (3) 若表达式是将亡值(xvalue),返回 T&&。
    • decltype 的注意事项:
      • 对变量名使用 decltype 是特例,直接返回其声明类型,不加引用;而一旦变量名套上括号变成 (x),就退化为普通左值表达式,得到 int&;这正是 decltype(x) 与 decltype((x)) 结果不同的根本原因。
      • decltype 只做类型分析,不求值,因此像 x++、x = 5、*p 这类表达式不会产生任何副作用。
  • decltype 作用于表达式的常见案例:

    表达式类别decltype 使用示例推导结果说明
    左值decltype((x))int&加括号后 x 退化为左值表达式,返回引用
    左值decltype(x = 5)int&赋值表达式返回左值,结果为引用
    左值decltype(++x)int&前置自增返回左值,结果为引用
    左值decltype(*p)int&解引用返回左值,结果为引用
    左值decltype(true ? x : x)int&两个分支均为左值时,三元表达式为左值
    左值decltype((void)x, x)int&逗号表达式取最后一个表达式的类型和值类别
    纯右值decltype(x++)int后置自增返回纯右值,结果非引用
    纯右值decltype(x + y)int算术运算返回纯右值,结果非引用
    纯右值decltype(f())(f 返回非引用)函数返回类型函数返回纯右值时,结果为返回类型本身
    将亡值decltype(std::move(x))int&&std::move 返回右值引用,结果为 int&&
    将亡值decltype(static_cast<int&&>(x))int&&强制转为右值引用,结果为 int&&
推导案例
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <boost/type_index.hpp>
#include <iostream>

// 辅助打印推导类型(保留 const/volatile/引用 属性)
template <typename T>
void print_type(const char* name) {
std::cout << name << " = " << boost::typeindex::type_id_with_cvr<T>().pretty_name() << std::endl;
}

int main() {
int x = 0;
int& r = x;
const int c = 1;

// 案例 1:decltype 作用于字面量(纯右值)——返回其类型本身,非引用
decltype(5) a = 0; // decltype(5) = int,a 类型为 int
print_type<decltype(a)>("decltype(a)");

// 案例 2:decltype 作用于左值表达式(带括号的变量),得到引用类型
decltype((x)) b = x; // 变量名 x 单独用时是 int,但 (x) 是左值表达式,decltype((x)) 返回 int&,b 类型为 int&
print_type<decltype(b)>("decltype(b)");

// 案例 3:decltype 作用于赋值表达式——赋值返回左值,得到引用类型
decltype(x = 5) d = x; // decltype(x = 5) = int&,d 类型为 int&
print_type<decltype(d)>("decltype(d)");

// 案例 4:decltype 作用于前置自增表达式——返回左值,得到引用类型
decltype(++x) e = x; // decltype(++x) = int&,e 类型为 int&
print_type<decltype(e)>("decltype(e)");

// 案例 5:decltype 作用于后置自增表达式——返回纯右值,得到非引用类型
decltype(x++) f = 0; // decltype(x++) = int,f 类型为 int
print_type<decltype(f)>("decltype(f)");

// 案例 6:decltype 作用于算术表达式——返回纯右值,得到非引用类型
decltype(x + c) g = 0; // decltype(x + c) = int,g 类型为 int
print_type<decltype(g)>("decltype(g)");

// 案例 7:decltype 作用于返回右值引用的函数调用(结果为将亡值)
decltype(std::move(x)) h = 0; // std::move 返回 int&&,h 类型为 int&&
print_type<decltype(h)>("decltype(h)");

// 案例 8:decltype 作用于解引用表达式——返回左值,得到引用类型
int* p = &x;
decltype(*p) i = x; // decltype(*p) = int&,i 类型为 int&
print_type<decltype(i)>("decltype(i)");

// 案例 9:decltype 作用于三元表达式——两个分支均为左值时返回左值
decltype(true ? x : x) j = x; // decltype(true ? x : x) = int&,j 类型为 int&
print_type<decltype(j)>("decltype(j)");

// 案例 10:decltype 作用于逗号表达式——结果为最后一个表达式的类型和值类别
decltype((void)x, x) k = x; // 逗号表达式结果为左值 x,k 类型为 int&
print_type<decltype(k)>("decltype(k)");

return 0;
}

程序运行的结果如下:

1
2
3
4
5
6
7
8
9
10
decltype(a) = int
decltype(b) = int&
decltype(d) = int&
decltype(e) = int&
decltype(f) = int
decltype(g) = int
decltype(h) = int&&
decltype(i) = int&
decltype(j) = int&
decltype(k) = int&
作用域函数名
推导规则
  • decltype 作用于函数名(未加括号)的推导规则:

    • 返回函数类型本身,而非函数指针,也不会发生函数到函数指针的退化。
    • 要点:
      • decltype(f) 与 decltype(&f) 不同:前者是返回函数类型 int(),后者是返回函数指针 int(*)()。
      • 这与 auto 也不同:auto x = f; 中 f 会退化为函数指针。
      • 函数名本身是左值,但 decltype 对未加括号的函数名走的是 “实体声明类型” 特例,因此不加引用。
      • 一旦函数名加上括号 decltype((f)),就会退化为左值表达式,然后按普通左值表达式的规则处理,得到函数引用。
      • decltype 只做类型分析,不求值,因此不会真正调用函数。
  • decltype 作用于函数名(未加括号)的常见案例:

    decltype 使用示例推导结果说明
    int f(); → decltype(f)int()返回函数类型本身
    int& f(); → decltype(f)int&()保留返回引用,但仍是函数类型
    void f(int); → decltype(f)void(int)参数列表也是函数类型的一部分
    int f(); → decltype(f)*int(*)()需要显式加 * 才能得到函数指针
    int f(); → decltype(f)&int(&)()显式加 & 才能得到函数引用
    int f(); → decltype((f))int(&)()加括号后退化为左值表达式,返回函数引用
推导案例
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <boost/type_index.hpp>
#include <functional>
#include <iostream>

int f() {
return 0;
}

int& g() {
static int v = 0;
return v;
}

void h(int) {
}

// 辅助打印推导类型(保留 const/volatile/引用 属性)
template <typename T>
void print_type(const char* name) {
std::cout << name << " = " << boost::typeindex::type_id_with_cvr<T>().pretty_name() << std::endl;
}

int main() {
// 案例 1:decltype 作用于返回 int 的函数名——返回函数类型本身
decltype(f) a; // decltype(f) = int(),a 类型为 int()
print_type<decltype(a)>("decltype(a)");

// 案例 2:decltype 作用于返回 int& 的函数名——保留返回引用
decltype(g) b; // decltype(g) = int&(),b 类型为 int&()
print_type<decltype(b)>("decltype(b)");

// 案例 3:decltype 作用于带参数的函数名——参数列表也是函数类型的一部分
decltype(h) c; // decltype(h) = void(int),c 类型为 void(int)
print_type<decltype(c)>("decltype(c)");

// 案例 4:decltype 作用于函数名后显式加 *——得到函数指针类型
decltype(f)* d = f; // decltype(f)* = int(*)(), d 类型为 int(*)()
print_type<decltype(d)>("decltype(d)");

// 案例 5:decltype 作用于函数名后显式加 &——得到函数引用类型
decltype(f)& e = f; // decltype(f)& = int(&)(), e 类型为 int(&)()
print_type<decltype(e)>("decltype(e)");

// 案例 6:decltype 作用于加括号的函数名——退化为左值表达式,返回函数引用
decltype((f)) i = f; // decltype((f)) = int(&)(), i 类型为 int(&)()
print_type<decltype(i)>("decltype(i)");

// 案例 7:decltype 结合 std::function 使用
std::function<decltype(f)> j = f; // decltype(f) = int(),作为模板参数传入 std::function,得到 std::function<int()>,j 类型为 std::function<int()>,可用于包装函数 f
print_type<decltype(j)>("decltype(j)");

return 0;
}

程序运行的结果如下:

1
2
3
4
5
6
7
decltype(a) = int ()
decltype(b) = int& ()
decltype(c) = void (int)
decltype(d) = int (*)()
decltype(e) = int (&)()
decltype(i) = int (&)()
decltype(j) = std::function<int ()>

使用案例

案例代码一
  • decltype 应付可变类型
案例代码二
  • decltype 通过变量表达式抽取变量类型
案例代码三
  • decltype(auto) 用于变量声明

  • decltype(auto) 用于函数返回类型
案例代码四
  • auto 结合 decltype 构成返回类型后置语法