- 作者:老汪软件技巧
- 发表时间:2024-09-23 15:01
- 浏览量:
C++11开始引入了 decltype 关键字,和 auto一样,它也是用于获取和推断对象和表达式类型的。
它们两者在类型推断上,有什么不同呢?
左值、右值、将亡值
因为 auto 和 decltype 的类型推导规则涉及到对 表达式值类别的区分,这里先简单介绍C++11以后的左值、右值和将亡值概念。 如果更具体的细分,除了上面三种值类别,还有泛左值和泛右值,这两个是在纯左值和右值的基础上,加上将亡值。
左值特征左值类型纯右值
在前面有提过,自C++11开始,纯右值(pvalue, pure ravlue)相当于之前的右值,那么什么是纯右值呢?
右值类型将亡值
将亡值(xvalue, eXpiring value),顾名思义即将消亡的值,是C++11新增的跟右值引用相关的表达式,通常是将要被移动的对象(移为他用),比如返回右值引用T&&的函数返回值、std::move的返回值,或者转换为T&&的类型转换函数的返回值。
xvalue 只能通过两种方式来获得,这两种方式都涉及到将一个左值赋给(转化为)一个右值引用:
表达式
C/C++代码是由标识符、表达式和语句以及一些必要的符号(大括号等)组成。
表达式由按照语言规则排列的运算符,常量和变量组成。一个表达式可以包含一个或多个操作数,零个或多个运算符来计算值。单一变量是最简单的一种表达式。
auto
C++11 重新定义了auto的含义以后,在之后发布的C++14、C++17等标准对auto的更新、增强的功能
auto 的 “自动推导” 能力只能用在 “初始化” 的场合。包括 赋值初始化 或者 花括号初始化 ( Initializer list ),变量右边必须是一个表达式。如果纯是一个变量声明,则无法使用 auto 。
auto 的推导规则有些复杂,跟函数模板参数的自动推导有相似之处( 具体函数模板参数自动推导规则可以参考《Effective Modern C++》条例2 )。
推导规则
使用auto进行变量类型自动推导的格式一般如下:
[const,volatile] auto [*, &, &&] var = expr; // []内的内容表示可选
const 与 volatile修饰词,这两个称为CV修饰词或者CV限定符。
按照上面的形式,根据 "=" 左边auto的修饰情况分为三种情形:
int a = 1;
int& b = a;
const int& c = a;
// 忽略=号右边的引用,auto被推导为int,d是int类型
auto d = b;
// 忽略=号右边的const属性和引用,auto被推导为int,e是int类型
auto e = c;
int x = 1;
const int cx = x;
const int& crx = x;
auto& i = x; // (1) i为 int&
auto& ci = cx; // (2) ci为 const int&
auto* pi = &crx; // (3) pi为 const int*
除了下面的第3种情况外 (万能引用),auto都不会推导出结果是引用的类型,如果要定义为引用类型,就要像上面那样明确地写出来。
但是auto可以推导出来是指针类型,也就是说就算没有明确写出 auto*,如果 expr 的类型是指针类型的话,auto则会被推导为指针类型,这时expr的const属性也会得到保留,如下的例子:
int i = 1;
auto pi = &i; // pi为int*
const char word[] = "Hello world!";
auto str = word; // str为const char*
pi 被推导出来的类型为 int*,而 str 被推导出来的类型为 const char*。
这种情况下,其推导规则与模板类型的推导规则很相似,参考 《Effective Modern C++》条例1 。
当以 auto&& 的形式出现时,它表示的是万能引用而非右值引用,这时将视 expr 的类型分为两种情况:
int x = 1;
const int cx = x;
auto&& ref1 = x; // (1) ref1为int&
auto&& ref2 = cx; // (2) ref2为const int&
auto&& ref3 = 2; // (3) ref3为int&&
decltype
decltype 与 auto 关键字一样,用于进行 编译时类型推导,不过它与 auto 还是有一些区别的:
auto 通过初始化它的表达式来推断变量的类型,也即 auto 推导变量依赖于初始化它的表达式,并且auto声明的变量必须初始;
decltype 的类型推导是 以一个普通表达式作为参数,返回该表达式的类型, 而且 decltype 并不会对表达式进行求值。
decltype 进行类型推导的形式一般如下:
decltype(expr) v [ = another_expr]; // []内的内容表示可选
除了C++14开始支持的 decltype(auto)这种形式,其他情况下 decltye(expr) 可以与CV限定符以及&、 等联用*,比如:
int x = 100;
decltype(x)* px = &x;
const decltype(x)& crx = x;
推导规则
使用 decltype(expr) 获取类型时,编译器将根据以下规则得出结果:
如果 expr 是一个单独变量**(结构化绑定除外)或者类成员访问**。变量带有括号,则 decltype(expr) 返回变量定义时的类型(保持 const 和引用属性)变量不带括号,则 decltype(expr) 返回 T&。如果 expr 是一个 函数或者仿函数 ==调用==, decltype(expr)返回函数返回值的类型。如果 expr 是其他类型为 T 的表达式,且:若 expr 的值类别为 左值(lvalue),则 decltype 产生 T& ;若 expr 的值类别为 右值(rvalue),则 decltype 产生 T ;若 expr 的值类别为 将亡值(xvalue), 则 decltype 产生 T&& 。
[!NOTE]
对于 expr 是 单纯变量的情况, decltype(x) 和 decltype((x)) 通常是不同的类型。
struct Foo { int a; };
using Func = Foo& ();
using Array = char[2];
const Foo f_v();
Foo& f_ref();
Foo&& f_r();
int minus(int x, int y) {
return x - y;
}
int a = 0;
const int b = 1;
const Foo foo = {10};
Foo&& rref = Foo{1};
const Foo& ref = foo;
char c[2] = {1, 2};
int* p = &a;
const Foo* pFoo = &foo;
// 1 单个变量
// 1.1 不带括号的单个变量
decltype(a) v1; // int
decltype(b) v2; // const int
decltype(foo) v3; // const Foo
decltype(ref) v4; // const Foo&
decltype(rref) v5; // Foo&&
decltype(c) v6; // char[2]
decltype(p) v7; // int*
decltype(foo.a) v8; // int
// 1.2 带括号的单个变量
decltype((a)) v1_2; // int&
decltype((foo)) v2_2; // const Foo&
decltype((foo.a)) v3_2; // const int&
// 2 函数调用
decltype(f_v()) v10; // const Foo
// 3.1 左值
decltype(a += 10) v11; // int&
decltype(++a)) v12; // int&
decltype(c[1]) v13; // char&
decltype(*p) v14; // int&
// 推导函数类型
decltype(minus) v15_1; // int(int,int)
decltype((minus)) v15_2; // int(&)(int, int)
decltype(minus)* v15_2; // int(*)(int, int)
// 3.2 右值
decltype(a++) v16; // int
decltype(&b) v17; // const int*
decltype(1+2) v18; // int
// 3.3 将亡值
decltype(std::move(a)) v19; // int&&
当 decltype(expr) 传入的是 函数名时,需要注意⚠️:虽然 C/C++ 多数时候会 隐式の把函数名转换成对应的函数指针,但这两者本身并不等价。
decltype(func_name) 推导出来的并不是 函数指针,而是函数类型!
比如上面代码中的 decltype(minus) , 得到的时候 函数 minus 的类型:int(int, int) ,而其对应的函数指针形式应该是 int(*)(int, int) ;
如果想得到函数指针,需要显式的加上 '*' : decltype(minus)* func_ptr; 。
其他用法decltype(auto)
C++14 运行时用 decltype(auto) 告诉编译器使用 decltype 的规则来推导 auto.
decltype(auto) 必须单独使用,也就是不能再给它加上指针、引用还有 cv 属性。
template <typename _Tx, typename _Ty>
auto multiply(_Tx x, _Ty y) -> decltype(_Tx*_Ty) {
return x * y;
}
与using、typedef结合使用
using size_t = decltype(sizeof(0));
using nullptr_t = decltype(nullptr);
vector<int> vec;
typedef decltype(vec.begin()) vectype;
for(vectype i = vec.begin(); i != vec.end(); i++) {
//...
}
参考