编译器将根据以下三条规则得出结果:
( )
包围的表达式,或者是一个类成员访问表达式,或者是一个单独的变量,那么 decltype(exp) 的类型就和 exp 一致,这是最普遍最常见的情况。( )
包围,那么 decltype(exp) 的类型就是 exp 的引用;假设 exp 的类型为 T,那么 decltype(exp) 的类型就是 T&。【实例】exp 是左值,或者被( )包围:
using namespace std;
class Base{
public:
int x;
};
int main(){
const Base obj;
//带有括号的表达式
decltype(obj.x) a = 0; //obj.x 为类的成员访问表达式,符合推导规则一,a 的类型为 int
decltype((obj.x)) b = a; //obj.x 带有括号,符合推导规则三,b 的类型为 int&。
//加法表达式
int n = 0, m = 0;
decltype(n + m) c = 0; //n+m 得到一个右值,符合推导规则一,所以推导结果为 int
decltype(n = n + m) d = c; //n=n+m 得到一个左值,符号推导规则三,所以推导结果为 int&
return 0;
}
用法
在难以或不可能以标准写法进行声明的类型时,decltype 很有用,例如 lambda 相关类型或依赖于模板形参的类型。C++11新标准引入了decltype类型说明符,它的作用是选择并返回操作数的数据类型,在此过程中,编译器分析表达式并得到它的类型,却不实际计算表达式的值。
据我自己的理解,这个关键字经常被用在模板编程中,模板在使用时,可能会出现不知道应该声明是什么类型的状况
template
?type? fun(T1 a,T2 b)
{
?type? aplusb=a+b;
return aplusb;
}
在上述情况中,我们事先并不知道aplusb的类型,无法对其进行声明。
但是C++11中新增加的关键字decltype解决了这个问题
template
auto fun(T1 a,T2 b) ->decltype(a+b) //auto 马上会讲
{
return aplusb=a+b;
}
为什么函数的返回值是auto嘞?由于在提供返回类型之前,还未声明变量a,b所以无法对返回类型设置为decltype(a,b)
这时候在C++11中提供了一个解决方案,就是后置返回类型。简单的说就是把返回值后置。
例子
#include
struct A { double x; };
const A* a;
decltype(a->x) y; // y 的类型是 double(其声明类型)
decltype((a->x)) z = y; // z 的类型是 const double&(左值表达式)???
template
auto add(T t, U u) -> decltype(t + u) // 返回类型依赖于模板形参
{ // C++14 开始可以推导返回类型
return t+u;
}
int main()
{
int i = 33;
decltype(i) j = i * 2;
std::cout << "i = " << i << ", "
<< "j = " << j << '\n';
auto f = [](int a, int b) -> int
{
return a * b;
};
decltype(f) g = f; // lambda 的类型是独有且无名的
i = f(2, 2);
j = g(3, 3);
std::cout << "i = " << i << ", "
<< "j = " << j << '\n';
}
输出:
i = 33, j = 66
i = 4, j = 9
auto 的语法格式比 decltype 简单,所以在一般的类型推导中,使用 auto 比使用 decltype 更加方便,本节仅演示只能使用 decltype 的情形。
我们知道,auto 只能用于类的静态成员,不能用于类的非静态成员(普通成员),如果我们想推导非静态成员的类型,这个时候就必须使用 decltype 了。下面是一个模板的定义:
#include
using namespace std;
template
class Base {
public:
void func(T& container) {
m_it = container.begin();
}
private:
typename T::iterator m_it; //注意这里
};
int main()
{
const vector v;
Base> obj;
obj.func(v);
return 0;
}
单独看 Base 类中 m_it 成员的定义,很难看出会有什么错误,但在使用 Base 类的时候,如果传入一个 const 类型的容器,编译器马上就会弹出一大堆错误信息。原因就在于,T::iterator
并不能包括所有的迭代器类型,当 T 是一个 const 容器时,应当使用 const_iterator。
要想解决这个问题,在之前的 C++98/03 版本下只能想办法把 const 类型的容器用模板特化单独处理,增加了不少工作量,看起来也非常晦涩。但是有了 C++11 的 decltype 关键字,就可以直接这样写:
template
class Base {
public:
void func(T& container) {
m_it = container.begin();
}
private:
decltype(T().begin()) m_it; //注意这里
};
看起来是不是很清爽?
注意,有些低版本的编译器不支持T().begin()
这种写法,以上代码我在 VS2019 下测试通过,在 VS2015 下测试失败。
用法
C++11中,auto不再是一个存储类型指示符,而是一个自动推导变量的类型。auto并非是一种类型的声明,而是一个类型声明时的“占位符”,编译器在编译期间会将auto替换为变量实际的类型。
我觉得auto给我们带来的最大改变有两方面。
解放程序员双手。有了auto这个神器,妈妈再也不用担心我的键盘手了。auto可以代替以前要打的很长很长的变量类型。
还是用在模板推导,下边的例子如果没有auto,x*y就没有办法填写类型了。
template
void Multiply(_Tx x, _Ty y)
{
auto v = x*y;
std::cout << v;
}
需要注意的是:
auto 变量必须在定义时初始化,这类似于const关键字。
如果初始化表达式是引用,则去除引用语义。
如果初始化表达式为const或volatile(或者两者兼有),则除去const/volatile语义。
如果auto关键字带上&号,则不去除const语意。
const int a2 = 10;
auto &b2 = a2;//因为auto带上&,故不去除const,b2类型为const int
初始化表达式为数组时,auto关键字推导类型为指针。
若表达式为数组且auto带上&,则推导类型为数组类型。
函数或者模板参数不能被声明为auto
void func(auto a) //错误
{
//...
}
时刻要注意auto并不是一个真正的类型。auto仅仅是一个占位符,它并不是一个真正的类型,不能使用一些以类型为操作数的操作符,如sizeof或者typeid。
cout << sizeof(auto) << endl;//错误
cout << typeid(auto).name() << endl;//错误
例子
#include
#include
template
auto add(T t, U u) { return t + u; } // 返回类型是 operator+(T, U) 的类型
// 在其所调用的函数返回引用的情况下
// 函数调用的完美转发必须用 decltype(auto)
template
decltype(auto) PerfectForward(F fun, Args&&... args)
{
return fun(std::forward
(args)...); }
template
// C++17 auto 形参声明 auto f() -> std::pair
// auto 不能从花括号初始化器列表推导 {
return {n, n};
}
int main()
{
auto a = 1 + 2; // a 的类型是 int
auto b = add(1, 1.2); // b 的类型是 double
static_assert(std::is_same_v
); static_assert(std::is_same_v
); auto c0 = a; // c0 的类型是 int,保有 a 的副本
decltype(auto) c1 = a; // c1 的类型是 int,保有 a 的副本
decltype(auto) c2 = (a); // c2 的类型是 int&,为 a 的别名
std::cout << "a, before modification through c2 = " << a << '\n';
++c2;
std::cout << "a, after modification through c2 = " << a << '\n';
auto [v, w] = f<0>(); // 结构化绑定声明
auto d = {1, 2}; // OK:d 的类型是 std::initializer_list
auto n = {5}; // OK:n 的类型是 std::initializer_list
// auto e{1, 2}; // C++17 起错误,之前为 std::initializer_list
auto m{5}; // OK:C++17 起 m 的类型为 int,之前为 initializer_list
// decltype(auto) z = { 1, 2 } // 错误:{1, 2} 不是表达式
// auto 常用于无名类型,例如 lambda 表达式的类型
auto lambda = [](int x) { return x + 3; };
// auto int x; // 于 C++98 合法,C++11 起错误
// auto x; // 于 C 合法,于 C++ 错误
}
可能的输出:
a, before modification through c2 = 3
a, after modification through c2 = 4
auto 和 decltype 关键字都可以自动推导出变量的类型,但它们的用法是有区别的:
auto varname = value;
decltype(exp) varname = value;
其中,varname 表示变量名,value 表示赋给变量的值,exp 表示一个表达式。
auto 根据=
右边的初始值 value 推导出变量的类型,而 decltype 根据 exp 表达式推导出变量的类型,跟=
右边的 value 没有关系。