声明:该笔记是在学习《深入理解C++11》、《C++11/14高级编程 Boost程序库探秘》时做的总结,方便以后巩固复习!
二、decltype类型推导
2.1、 decltype
auto关键字能够在赋值语句里推导类型,但这只是C++语言里一种很少见的应用场景,要想在任意的场景都能够得到表达式的类型就需要使用关键字:decltype
decltype的形式和函数调用很像:
decltype(expression)
decltype在技术和使用上和sizeof非常像,都需要编译器在编译期计算类型,但是sizeof返回的是整数,而decltype得到的是类型。
decltype和auto的异同:
与auto不同点:
decltypr的类型推导并不是像auto一样是从变量声明的初始化表达式获得变量的类型,decltype总是以一个普通的表达式为参数,返回该表达式的类型。
与auto相同点:
①、作为一个类型指示符,decltype可以将获得的类型来定义另外一个变量;
②、declttype类型推导也是在编译时进行的
#include <iostream>
#include <typeinfo>
using namespace std;
int main()
{
int a;
decltype(a) b = 0;
cout<<"a的类型是"<<typeid(a).name()<<endl;
cout<<"b的类型是"<<typeid(b).name()<<endl;
float c;
double d;
decltype(c + d ) e;
cout<<"e的类型是"<<typeid(e).name()<<endl;
return 0;
}
输出:
a的类型是i
b的类型是i
e的类型是d
可以看到变量b的类型由decltype(a)进行声明,表示b跟a这个表达式返回的类型相同;而e的类型则由(c + d)这个表达式返回的类型相同,c + d的表达式的类型为double,所以e的类型被decltype推导为double。
2.2、decltype的应用
①、decltype和typedf/using合用
在C++11 的头文件中常可以看到以下代码:
using size_t = decltype(sizeof(0));
using ptrdiff_t = decltype((int*)0- (int*)0);
using nullptr_t = decltype(nullptr);
size_t以及ptrdiff_t及nullptr都是由decltype推导出来的类型 这种定义方式的意义: 在一些常量、基本类型、运算符、操作符等基于被定义好的情况下,类型可以按照规则被推导出来。而使用using可以为这些类型取名;这样就颠覆了之前类型扩展需要将类型“映射”到基本类型的常规做法。
②、deltype在某些场景下使用增加代码的可读性
#include <vector>
using namespace std;
int main()
{
vector<int> vec;
typedef decltype(vec.begin()) vectype;
for (vectype i = vec.begin(); i < vec.end(); i++)
{
......
}
for (decltype(vec)::iterator i = vec.begin(); i < vec.end(); i++)
{
......
}
定义了vector的iterator的类型。这个类型还可以在main函数中重用。 当遇到一些具有复杂类型的变量或表达式时,就可以利用decltype和typedef/using的组合来将其转化为一个简单的表达式,这样在以后的代码写作中可以提高可读性和可维护性。 此外可以看到decltype(vec)::iterator这样的灵活用法,这看起来跟auto非常类似,也类似于是一种“占位符”式的替代。
③、使用decltype重用匿名类型
enum class
{
K1, K2, K3
}anon_e;
union {
decltype(anon_e) key;
char* name;
}anon_u;
struct {
int d;
decltype(anon_u) id;
}anon_s[100];
int main()
{
decltype(anon_s) as;
as[0].id.key = decltype(anon_e)::K1;
return 0;
}
这里我们使用了3种不同的匿名类型:匿名的强类型枚举anon_e、匿名的联合体anon_u,以及匿名的结构体数组anon_s。可以看到,只要通过匿名类型的变量名anon_e、anon_u,以及anon_s,decltype可以推导其类型并且进行重用。这些都是以前C++代码所做不到的。 不过匿名一般都有匿名理由,一般都不希望匿名后的类型被重用。
④、decltype可以适当扩大模板泛型的能力
#include <iostream>
#include <typeinfo>
using namespace std;
template<typename T1, typename T2>
void Sum(T1 & t1, T2 & t2, decltype(t1 + t2) & s)
{
s = t1 + t2;
cout<<"s的类型是"<<typeid(s).name()<<endl;
}
int main()
{
int a = 3;
long b = 5;
float c = 1.0f, d = 2.3f;
long e;
float f;
Sum(a, b, e);
Sum(c, d, f);
}
输出:
s的类型是l
s的类型是f
代码中的Sum函数模板增加了类型为decltype(t1+t2)的s作为参数,而函数本身不返回任何值。这样一来,Sum的适用范围增加,其返回的类型是根据t1 + t2推导而来的类型。不过这里还是有一定的限制,可以看到返回值的类型必须一开始就被指定,我们必须清楚Sum运算的结果使用什么样的类型来存储是合适的,这在一些泛型编程中依然不能满足要求。 解决的方法是结合decltype与auto关键字,使用追踪返回类型的函数定义来使得编译器对函数返回值进行推导。
2.3、decltype推导规则
①、表达式为普通变量或者普通表达式或者类表达式,在这种情况下,使用 decltype 推导出的类型和表达式的类型是一致的。
#include <iostream>
#include <string>
using namespace std;
class Test
{
public:
string text;
static const int value = 110;
};
int main()
{
int x = 99;
const int &y = x;
decltype(x) a = x;
decltype(y) b = x;
decltype(Test::value) c = 0;
Test t;
decltype(t.text) d = "hello, world";
return 0;
}
变量 a 被推导为 int 类型 变量 b 被推导为 const int & 类型 变量 c 被推导为 const int 类型 变量 d 被推导为 string 类型
②、表达式是函数调用,使用 decltype 推导出的类型和函数返回值一致。
class Test{...};
int func_int();
int& func_int_r();
int&& func_int_rr();
const int func_cint();
const int& func_cint_r();
const int&& func_cint_rr();
const Test func_ctest();
int n = 100;
decltype(func_int()) a = 0;
decltype(func_int_r()) b = n;
decltype(func_int_rr()) c = 0;
decltype(func_cint()) d = 0;
decltype(func_cint_r()) e = n;
decltype(func_cint_rr()) f = 0;
decltype(func_ctest()) g = Test();
变量 a 被推导为 int 类型 变量 b 被推导为 int& 类型 变量 c 被推导为 int&& 类型 变量 d 被推导为 int 类型 变量 e 被推导为 const int & 类型 变量 f 被推导为 const int && 类型 变量 g 被推导为 const Test 类型
函数 func_cint () 返回的是一个纯右值(在表达式执行结束后不再存在的数据,也就是临时性的数据),对于纯右值而言,只有类类型可以携带const、volatile限定符,除此之外需要忽略掉这两个限定符,因此推导出的变量 d 的类型为 int 而不是 const int。
③、表达式是一个左值,或者被括号 ( ) 包围,使用 decltype 推导出的是表达式类型的引用(如果有 const、volatile 限定符不能忽略)。
#include <iostream>
#include <vector>
using namespace std;
class Test
{
public:
int num;
};
int main()
{
const Test obj;
decltype(obj.num) a = 0;
decltype((obj.num)) b = a;
int n = 0, m = 0;
decltype(n + m) c = 0;
decltype(n = n + m) d = n;
return 0;
}
obj.num 为类的成员访问表达式,符合场景 1,因此 a 的类型为 int obj.num 带有括号,符合场景 3,因此 b 的类型为 const int&。 n+m 得到一个右值,符合场景 1,因此 c 的类型为 int n=n+m 得到一个左值 n,符合场景 3,因此 d 的类型为 int&
2.4、cv限制符的继承与冗余的符号
①、与auto类型推导时不能“带走”cv限制符不同是:
decltype是能够“带走”表达式的cv限制符的。不过,如果对象的定义中有const或volatile限制符,使用decltype进行推导时,其成员不会继承const或volatile限制符。
#include <type_traits>
#include <iostream>
using namespace std;
const int ic = 0;
volatile int iv;
struct S
{
int i;
};
const S a = {0};
volatile S b;
volatile S* p = &b;
int main() {
cout << is_const<decltype(ic)>::value << endl;
cout << is_volatile<decltype(iv)>::value << endl;
cout << is_const<decltype(a)>::value << endl;
cout << is_volatile<decltype(b)>::value << endl;
cout << is_const<decltype(a.i)>::value << endl;
cout << is_volatile<decltype(p->i)>::value << endl;
}
输出:
1
1
1
1
0
0
这里使用了C++库提供的is_const和is_volatile来查看类型是否是常量或者易失的。可以看到,结构体变量a、b和结构体指针p的cv限制符并没有出现在其成员的decltype类型推导结果中。
与auto相同的是:
decltype从表达式推导出类型后,进行类型定义时,也会允许一些冗余的符号。 比如cv限制符以及引用符号&,通常情况下,如果推导出的类型已经有了这些属性,冗余的符号则会被忽略
#include <type_traits>
#include <iostream>
using namespace std;
int i = 1;
int & j = i;
int * p = &i;
const int k = 1;
int main()
{
decltype(i) & var1 = i;
decltype(j) & var2 = i;
cout << is_lvalue_reference<decltype(var1)>::value << endl;
cout << is_rvalue_reference<decltype(var2)>::value << endl;
cout << is_lvalue_reference<decltype(var2)>::value << endl;
decltype(p)* var3 = &p;
auto* v3 = p;
v3 = &i;
const decltype(k) var4 = 1;
}
输出:
1
0
1
这里定义了类型为decltype(i) &的变量var1,以及类型为decltype(j) &的变量var2。 由于i的类型为int,所以这里的引用符号保证var1成为一个int&引用类型。而由于j本来就是一个int &的引用类型,所以decltype之后的&成为了冗余符号,会被编译器忽略,因此j的类型依然是int &。 特别要注意的是decltype§的情况。可以看到,在定义var3变量的时候,由于p的类型是int,因此var3被定义为了int**类型。这跟auto声明中,也可以是冗余的不同。在decltype后的号,并不会被编译器忽略。 var4中const可以被冗余的声明,但会被编译器忽略,同样的情况也会发生在volatile限制符上。
参考文章:https://subingwen.cn/cpp/autotype/#2-2-decltype%E7%9A%84%E5%BA%94%E7%94%A8
欢迎关注公众号:Kevin的嵌入式学习站
|