C++中的值类别与模板推导
C++ 中表达式的值类别
C++ 中的表达式有两个属性,分别是值类型(type)和值类别(value category),每个表达式都有着某些非引用类型,且每个表达式都属于三种主要的值类别之一,这三种值类别分别是:左值(lvalue),纯右值(prvalue),将亡值(xvalue)。
- 泛左值(glvalue,generalized lvalue):这种表达式决定一个对象或者函数的标识,比如变量名,函数名。
- 纯右值(prvalue,pure rvalue):
- 计算某个运算对象的内置运算符的值,这种 prvalue 没有结果对象。
- 初始化一个结果对象。
- 将亡值(xvalue,expiring value):将亡值是对象的资源被标记为可以被重复使用的泛左值。
- 左值(lvalue):左值指的是不是将亡值的泛左值。
- 右值(rvalue):纯右值和将亡值统称为右值。
左值
- 变量名,函数名或者数据成员,无视类型。无论变量是左值引用或者右值引用,由变量名称组成的表达式始终是一个左值表达式。
- 一个函数调用或者运算符重载表达式,它们的返回类型为左值引用,例如
std::cout << 1。 - 内置的赋值和复合赋值运算符,例如
a = 1,b += 1。 - 内置的前置递增或者递减运算符,
++a和--a。 - 字符串字面量。
- 一个类型转换表达式,目标类型为左值引用。
- 一个函数调用或者运算符重载表达式,它的返回类型为函数的右值引用。
- 一个类型转换表达式,它的目标类型为函数的右值引用。
纯右值
- 字面量,例如
42,true,nullptr。注意字符串字面量不是纯右值。 - 一个函数调用或者运算符重载表达式,它们的返回类型为非引用类型,例如
str.substring(1,2)。 a++和a--,内置的后缀递增或者递减运算符。a+b,a-b,a & b,a << b,其他内置的算术运算符。a && b,a || b,!a,内置的逻辑运算符。a < b,a == b,a > b,内置的比较运算符。&a,内置的取址运算符。- 一个类型转换表达式,目标类型为非引用类型,例如
static_cast<double>(x)。 this指针。- 枚举器。
- 标量类型的非类型模板参数。
- 一个
lamada expression。
将亡值
- 一个函数调用或者函数重载表达式,它的返回类型是一个右值引用,例如
std::move(x)(从C++11开始)。 - 一个类型转换表达式,它的目标类型为右值引用,例如
static_cast<char&&>(r)(从C++11开始)。 - 任何一个表明了临时对象的表达式,参考 temporary materialization(从C++17开始)。
temporary materialization 请参考这篇文章。
将亡值表达式也会标识一个对象,毕竟它也属于泛左值这个大类里面,与左值不同的是,将亡值会将对象标记为资源可复用的对象,这在移动语义里面起着至关重要的作用。
模板函数类型推导
在理解了值类别之后,就可以开始讨论模板函数的类型推导了。整个模板的类型推导十分复杂,这里不会覆盖整个模板推导,仅仅包含模板函数的推导,而且只与引用有关。
左值引用
考虑如下模板函数
template<typename T>
void foo(T& t)
{
}
这个模板函数的参数是左值引用,所以这个模板函数希望能够接收一个左值表达式。需要注意的是,即使你传递的是右值引用,它仍然还是一个左值表达式,如下代码所示:
int&& a = 1;
foo(a);
但是这种形式下,传右值是不可以的,因为如果要将左值引用绑定到一个右值,它必须是一个常量左值引用,因此,如下代码是正确的:
class Foo
{
};
template<typename T>
void Foo(const T& a);
foo(Foo());//纯右值
Foo f;
foo(std::move(f));//将亡值
引用转发
引用转发是形如以下的模板函数声明:
template<typename T>
void foo(T&& t)
{
}
我们用 T&& 形式来标识一个引用转发。引用转发的作用是,当你传递一个左值表达式时,模板类型参数会被推导为一个左值引用,例如如下代码:
int a = 1;
foo(a);//因为a是一个左值表达式,所以T被推导为int&
如果传递的是一个右值,那么模板类型参数会被推导为右值表达式的值类型,例如如下代码:
foo(1);//纯右值,T被推导为int
foo(std::move(1));//将亡值,T被推导为int
右值引用
右值引用非常类似于引用转发,它的形式如下:
template<typename T>
void foo(const T&& t)
{
}
右值引用比引用转发多了一个 const,这个模板希望接收一个右值表达式参数,因此以下的代码是不正确的:
int a =1;
foo(a);//我们传递了一个左值表达式,但是foo模板函数只能接收右值表达式
但是以下的代码是合法的:
foo(1);
int&& a=1;
foo(std::move(a));
int b = 1;
int& c = b;
foo(std::move(c));

浙公网安备 33010602011771号