C++ 从入门到精通之十九
大纲
- C++ 从入门到精通之一、C++ 从入门到精通之二、C++ 从入门到精通之三
- C++ 从入门到精通之四、C++ 从入门到精通之五、C++ 从入门到精通之六
- C++ 从入门到精通之七、C++ 从入门到精通之八、C++ 从入门到精通之九
- C++ 从入门到精通之十、C++ 从入门到精通之十一、C++ 从入门到精通之十二
- C++ 从入门到精通之十三、C++ 从入门到精通之十四、C++ 从入门到精通之十五
- C++ 从入门到精通之十六、C++ 从入门到精通之十七、C++ 从入门到精通之十八
- 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保留顶层 constvolatile int v;decltype(v)volatile int保留顶层 volatileconst volatile int cv;decltype(cv)const volatile int同时保留 const和volatileint& r = x;decltype(r)int&保留引用,不退化 const int& b = a;decltype(b)const int&同时保留引用和 constint&& rr = 0;decltype(rr)int&&保留右值引用 int arr[3];decltype(arr)int[3]保留数组类型,不退化为指针 int* p;decltype(p)int*指针类型本身 MyClass obj;decltype(obj)MyClass类类型对象
推导案例
1 |
|
程序运行的结果如下:
1 | decltype(c) = int |
作用于表达式
推导规则
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 |
|
程序运行的结果如下:
1 | decltype(a) = 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 |
|
程序运行的结果如下:
1 | decltype(a) = int () |
使用案例
案例代码一
decltype应付可变类型
案例代码二
decltype通过变量表达式抽取变量类型
案例代码三
decltype(auto)用于变量声明
decltype(auto)用于函数返回类型
案例代码四
auto结合decltype构成返回类型后置语法
