• 作者:老汪软件技巧
  • 发表时间: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进行变量类型自动推导的格式一般如下:

[constvolatile] 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++) {
   //...
}

参考