定义
c++11新增关键字decltype(declare type)声明类型, 功能是在编译时期进行自动类型推导
auto 和 decltype 关键字都可以自动推导出变量类型
auto 在推导类型时会根据value推导出变量类型, 要求变量必须初始化
auto vat_name = value;
decltype 根据 exp 表达式推导出变量类型, 跟 value 没有关系, 不要求变量必须初始化
decltype(exp) vat_name = value;
- 示例
int a = 0;
// b 被推导成了 int
decltype(a) b = 1;
// x 被推导成了 double
decltype(10.8) x = 5.5;
// y 被推导成了 double
decltype(x + 100) y;
decltype 能够根据变量、字面量、带有运算符表达式推导出变量类型, 但如果表达式的类型为 void, 则会导致编译错误
void func() {}
decltype(func()) x; // 错误, 返回类型是 void, 不能推导类型
推导规则
decltype 根据不同类型的表达式有不同的推导规则:
- 普通表达式和类成员访问表达式:
如果 exp 是一个普通表达式(如变量或常量), 则 decltype(exp) 推导结果与 exp 类型一致
如果 exp 是类成员访问表达式, 推导结果与该成员类型一致
- 函数调用:
如果 exp 是函数调用, 则 decltype(exp) 推导结果与该函数的返回类型一致
- 左值和括号表达式:
如果 exp 是一个左值或被括号包围, 则 decltype(exp) 推导结果为该表达式的引用类型
#include <iostream>
#include <string>
class Student {
public:
static int total;
int age;
float scores;
std::string name;
};
int Student::total = 0;
int main() {
int n = 0;
const int &r = n;
Student stu;
// 普通表达式: n 被推导为 int
decltype(n) a = n;
// 常量引用: r 被推导为 const int&
decltype(r) b = n;
// 类成员: Student::total 被推导为 int
decltype(Student::total) c = 0;
// 类成员: stu.name 被推导为 string
decltype(stu.name) url = "http://666.com";
return 0;
}
按照推导规则 1, 对于一般表达式, decltype 推导结果和这个表达式类型一致
函数调用
decltype可以根据函数调用推导返回值类型
注意, 函数调用时需要传入适当的参数, 但 decltype 只根据表达式的类型来推导, 并不会实际调用函数
// 函数声明
// 返回值为 int&
int& func_int_r(int, char);
// 返回值为 int&&
int&& func_int_rr(void);
// 返回值为 int
int func_int(double);
// 返回值为 const int&
const int& fun_cint_r(int, int, int);
// 返回值为 const int&&
const int&& func_cint_rr(void);
// decltype类型推导
int n = 100;
// a类型为 int&
decltype(func_int_r(100, 'A')) a = n;
// b类型为 int&&
decltype(func_int_rr()) b = 0;
// c类型为 int
decltype(func_int(10.5)) c = 0;
// x类型为 const int &
decltype(fun_cint_r(1,2,3)) x = n;
// y类型为 const int&&
decltype(func_cint_rr()) y = 0;
参数exp中调用函数时需要带上括号和参数, 但这仅仅是形式, 并不会真去执行函数代码
左值与括号表达式
当表达式是一个左值或括号包围的表达式时, decltype 会推导为其引用类型
#include <iostream>
class Base {
public:
int x;
};
int main() {
const Base obj;
// 带有括号表达式
// obj.x 为类成员访问表达式, 符合推导规则一, a 类型为 int
decltype(obj.x) a = 0;
// obj.x 带有括号, 符合推导规则三, b类型为 int&
decltype((obj.x)) b = a;
// 加法表达式
int n = 0, m = 0;
// n+m 得到一个右值, 符合推导规则一, 所以推导结果为 int
decltype(n + m) c = 0;
// n=n+m 得到一个左值, 符号推导规则三, 所以推导结果为 int&
decltype(n = n + m) d = c;
return 0;
}
左值是指那些在表达式执行结束后依然存在数据, 也就是持久性数据
右值是指那些在表达式执行结束后不再存在数据, 也就是临时性数据
对表达式取地址, 若编译器不报错就为左值, 否则为右值
应用
auto 只能用于类静态成员, 不能用于类非静态成员(普通成员), decltype 特别有用的一个场景是推导复杂类型, 如类的成员类型
- 示例: 解决容器迭代器类型推导
#include <iostream>
#include <vector>
template <typename T>
class Base {
public:
void func(T& container) {
// 使用 decltype 来推导容器的迭代器类型
m_it = container.begin();
}
private:
decltype(T().begin()) m_it; // 推导出迭代器类型
};
int main() {
const std::vector<int> v;
Base<const std::vector<int>> obj;
obj.func(v); // 正确, 推导出 const_iterator 类型
return 0;
}
- 示例: 非静态成员类型推导
在某些情况下, 需要获取类的非静态成员类型, decltype 提供了更强的支持
假设类成员变量的类型由外部传入:
template <typename T>
class Base {
public:
void func(T& container) {
// 使用 decltype 来推导成员类型
m_it = container.begin();
}
private:
decltype(T().begin()) m_it;
};