深入理解decltype和decltype(auto)
- 一、decltype语法介绍
- 二、decltype的推导规则
- 1. expr不加括号
- 2. expr加上括号
- 三、关于decltype的CV属性推导
- 四、 decltype(auto) 的使用
一、decltype语法介绍
decltype
关键字是C++11
新标准引入的关键字,它和关键字auto
的功能类似,也可以自动推导出给定表达式的类型,但它和auto
的语法有些不同,auto
推导的表达式放在=
的右边,并作为auto所定义的变量的初始值,而decltype
是和表达式结合在一起,语法如下:
decltype(expr) var;
它的语法像是函数调用,但它不是函数调用而是运算符,和sizeof
运算符类似,在编译期间获取他的类型,表达式expr
不会被真正执行,因此不会产生汇编代码。
decltype
和auto
在功能上大部分相似,但推导规则和应用场景存在一些区别:
- 用
auto
定义变量时必须提供初始值表达式,利用初始值表达式推导出类型并用它作为变量的初始值,使用auto
作为值语义的推导时,会忽略表达式expr的引用性和CV属性。 decltype
定义变量时可以不需要初始值。还有decltype
可以保留引用性和CV属性。
- 引用性:表达式的引用属性,如左值引用或者右值引用。
- CV 属性:指的是 const 和 volatile 修饰符。它们用于修饰类型,以指定对象的特殊属性和行为。
二、decltype的推导规则
decltype
的推导规则主要有三条规则:
- 如果
expr
一个类成员访问表达式,或者是一个单独的变量,decltype(expr)
的类型就和expr
一致,这是最普遍最常见的情况。 - 如果
expr
是函数调用,那么decltype(expr)
的类型就和函数返回值的类型一致。 - 如果
expr
是一个左值,或者被括号()
包围,那么decltype(expr)
的类型就是expr
的引用;假设expr
的类型为T
,么decltype(expr)
的类型就是T&
。
为了更好地理解 decltype
的推导规则,下面来看几个实际的例子。
1. expr不加括号
#include <iostream>
#include <type_traits>
int func(int x) {
return x;
}
class Base {
public:
int x = 0;
};
int main()
{
// 情况1
int i = 1;
const int& j = i;
decltype(j) x = j;
decltype(i) y = i;
// is_same是C++11之后的一个类模板,用于判断两个类型是否一致
cout << "x is const int& ? " << std::boolalpha << std::is_same<decltype(x), const int&>::value << endl;
cout << "y is int ? " << std::boolalpha <<std::is_same<decltype(y), int>::value << endl;
const Base b;
cout << "decltype(b.x) is int ? " << std::boolalpha << std::is_same<decltype(b.x), int>::value << endl;
int x1 = 1, x2 = 2;
cout << "decltype(x1 + x2) is int ? " << std::boolalpha << std::is_same<decltype(x1 + x2), int>::value << endl;
cout << "decltype(x1, x2) is int ? " << std::boolalpha <<std::is_same<decltype(x1, x2), int>::value << endl;
cout << "decltype(x1, 0) is int ? " << std::boolalpha << std::is_same<decltype(x1, 0), int>::value << endl;
// 情况2
cout << "decltype(func(1)) is int ? " << std::boolalpha << std::is_same<decltype(func(1)), int>::value << endl;
// 情况3
int a[10] = { 0 };
cout << "decltype(a[1] is int& ? " << std::boolalpha << std::is_same<decltype(a[1]), int&>::value << endl;
return 0;
}
2. expr加上括号
int main()
{
// 情况3
int x1 = 1;
int x2 = 2;
const Base b;
cout << "decltype((x1 + x2)) is int ? " << std::boolalpha << std::is_same<decltype((x1 + x2)), int>::value << endl;
cout << "decltype((x1)) is int& ? " << std::boolalpha << std::is_same<decltype((x1)), int&>::value << endl;
cout << "decltype((b.x)) is const int& ? " << std::boolalpha << std::is_same<decltype((b.x)), const int&>::value << endl;
return 0;
}
(1)式中相加后的结果是一个右值,加上括号后依然是一个右值,因此推导结果是int
。
(2)式中跟之前没有加括号的情况不一样,加上括号相当于是返回x1变量,因此是一个左值,推导结果是一个引用。
(3)式返回的是一个左值,推导结果是一个引用,但因为定义的类对象b是一个const对象,要保持它的内容不可被修改,因此引用要加上const修饰。
三、关于decltype的CV属性推导
- 通常情况下,
decltype(expr)
所推导的类型会同步expr的cv限定符 - 当
expr
是未加括号的成员变量时,对象表达式的cv限定符会被忽略
class Base {
public:
int x = 0;
};
int main()
{
// CV限定符的推导
const int i = 0;
cout << "decltype(i) is const int ? " << std::boolalpha << std::is_same<decltype(i), const int>::value << endl;
const Base b;
cout << "decltype(b.x) is int ? " << std::boolalpha << std::is_same<decltype(b.x), int>::value << endl;
cout << "decltype((b.x)) is const int& ? " << std::boolalpha << std::is_same<decltype((b.x)), const int&>::value << endl;
return 0;
}
为什么decltype((b.x))
是const int&
而不是const int
呢?
因为decltype
的第三条推导规则:被括号()
包围,会变成引用类型。
写到这里,不得不感概C++的语法是真的难,头都要给我学秃了…… 😭😭😭
四、 decltype(auto) 的使用
decltype(auto)
是 C++14 引入的一种类型推导机制,它结合了 decltype
和 auto
的特性。
使用 decltype(auto)
表示让auto
使用自动类型推导,但推导的规则是按照decltype
的规则来推导,这样在推导时会保留表达式的值的引用性和CV属性。
decltype(auto)
的使用语法规则如下:
decltype(auto) var = expr;
- 简单使用示例:
int i = 1;
const int& j = i;
decltype(auto) x = j; // x的类型为const int&
decltype(auto) y = i; // y的类型为int
decltype(auto)
用于推导函数返回值的类型
#include <iostream>
#include <vector>
std::vector<int> vec = {1, 2, 3, 4, 5};
// 返回左值引用
decltype(auto) getElementRef(size_t index) {
return vec[index];
}
// 返回右值
decltype(auto) getElementValue(size_t index) {
return vec[index] + 0; // 使用右值表达式
}
int main() {
// 使用 getElementRef 返回左值引用
decltype(auto) ref = getElementRef(2);
ref = 100; // 修改引用的值
std::cout << "vec[2]: " << vec[2] << std::endl; // 输出 100
// 使用 getElementValue 返回右值
decltype(auto) val = getElementValue(2);
std::cout << "val: " << val << std::endl; // 输出 100
return 0;
}
decltype(auto)
使用陷阱
最后,对于decltype(auto)能够推导函数返回值为引用类型这一点,需要提醒一下的是,小心会有下面的陷阱,如下面的函数:
decltype(auto) func() {
int x;
// do something...
return x;
}
这里推导出来的返回值类型是int,并且会拷贝局部变量x的值,这个没有问题。但如果是这样的定义:
decltype(auto) func() {
int x;
// do something...
return (x);
}
这个版本返回的是一个引用,它将引用到一个即将销毁的局部变量上,当这个函数返回后,所返回的引用将引用到一个不存在的变量上,造成引用空悬的问题,程序的结果将是未知的。无论是有意的还是无意的返回一个引用,都要特别小心。