【发布时间】:2014-08-22 12:00:45
【问题描述】:
移位运算符:<< >>
位运算符:~、&、^、|
sizeof 运算符:sizeof()
根据 C++ 标准 (n3797),我只能确认 ~ 产生纯右值 (5.3.1/2),而不是上面的其他值。
【问题讨论】:
标签: c++ operators expression language-lawyer
移位运算符:<< >>
位运算符:~、&、^、|
sizeof 运算符:sizeof()
根据 C++ 标准 (n3797),我只能确认 ~ 产生纯右值 (5.3.1/2),而不是上面的其他值。
【问题讨论】:
标签: c++ operators expression language-lawyer
据我所知,结果是 prvalues,但这只是推测。这似乎以与What is the value category of the operands of C++ operators when unspecified? 和Does the standard mandate an lvalue-to-rvalue conversion of the pointer variable when applying indirection? 中介绍的操作数的值类别类似的方式未指定。
我们可以从3.10部分看到左值和右值有如下注释:
第 5 章中对每个内置运算符的讨论表明 它产生的价值类别和价值类别 它期望的操作数。
但正如我们所见,5 部分仅在少数情况下明确说明了 值类别。在这个特定问题的情况下,仅明确说明& 和~ 结果的值类别。
即使它没有详细说明,我们也可以找到指向一致方向的线索。我们可以论证>>、<<、^ 和| 的操作数被转换为prvalues。这并不规定结果的值类别,但它似乎确实排除了结果是 xvalue 根据5 段落 7 部分,其中有以下注释:
一个表达式是一个 xvalue 如果它是:
——调用a的结果 函数,无论是隐式还是显式,其返回类型都是 对象类型的右值引用,
— 对右值引用的强制转换 对象类型,
——一个类成员访问表达式,指定一个 对象所在的非引用类型的非静态数据成员 表达式是一个 xvalue,或
— .* 指向成员的表达式 其中第一个操作数是 xvalue,第二个操作数是 指向数据成员的指针。
一般来说,这条规则的效果是 命名的右值引用被视为左值和未命名的右值 对对象的引用被视为 xvalues;右值引用 无论是否命名,函数都被视为左值。
我没有看到任何合理的论据表明结果可能是一个 lvalue,所以这基本上给我们留下了一个 prvalue。
所以具体如下:
~ 和 & 都包含在 5.3.1 部分 一元运算符 段落 2 中:
以下每个一元运算符的结果都是纯右值。
移位运算符需要在5.8 部分中介绍的积分提升移位运算符段落1说:
操作数应为整数或非范围枚举类型,并执行整数提升。
我们可以看到积分促销需要来自4.5 部分的prvalues 积分促销,它在每段开头都说:
一个prvalue [...]
^ 和 | 都需要通常的算术转换,并且都说:
运算符仅适用于整数或无范围枚举操作数
因此通常的算术转换的最后一个子句适用,它说:
否则,应在两个操作数上执行积分提升 (4.5)。59
经验方法
Luc Danton 在他对Empirically determine value category of C++11 expression? 的回答中使用经验方法来确定表达式的值类别。该方法使用以下代码:
template<typename T>
struct value_category {
// Or can be an integral or enum value
static constexpr auto value = "prvalue";
};
template<typename T>
struct value_category<T&> {
static constexpr auto value = "lvalue";
};
template<typename T>
struct value_category<T&&> {
static constexpr auto value = "xvalue";
};
// Double parens for ensuring we inspect an expression,
// not an entity
#define VALUE_CATEGORY(expr) value_category<decltype((expr))>::value
答案概括如下:
一个左值表达式产生一个左值引用类型,一个 xvalue 在 一个右值引用类型,以及一个仅在类型中的右值。
以下示例均产生prvalue (see it live):
int x = 10, y = 2 ;
int &xr = x ;
int &yr = y ;
std::cout << VALUE_CATEGORY( x << y ) << std::endl ;
std::cout << VALUE_CATEGORY( 10 << 2 ) << std::endl ;
std::cout << VALUE_CATEGORY( xr << yr ) << std::endl ;
std::cout << VALUE_CATEGORY( x | y ) << std::endl ;
std::cout << VALUE_CATEGORY( 10 | 2 ) << std::endl ;
std::cout << VALUE_CATEGORY( x ^ y ) << std::endl ;
std::cout << VALUE_CATEGORY( 10 ^ 2 ) << std::endl ;
std::cout << VALUE_CATEGORY( sizeof( int ) ) << std::endl ;
std::cout << VALUE_CATEGORY( sizeof( x ) ) << std::endl ;
【讨论】:
ints。所以,严格来说,[conv.prom] 对于ints 不需要 l-to-r - 但我认为说促销旨在为所有操作数产生纯右值并不是太牵强。跨度>
x += 1,x 被转换为重写形式 x = x + 1 的纯右值,但运算符仍然产生一个左值。同样,x++。我认为基本思想是算术和位运算符产生 与两个操作数不同的值。因此,将任一操作数作为左值返回是没有意义的。您可以使用赋值或显式构造(命名实体的;也:函数参数)创建具有“新”值的对象。
cppreference 对 functions 说如下:
prvaule(“纯”右值)是一个标识临时对象(或其子对象)的表达式或者是一个不与任何对象关联的值。
以下表达式是纯右值:
这适用于函数,我不确定关于内置运算符的具体规则是什么。
【讨论】:
sizeof() : size_t 根据标准 5.3.3 pt6 - 标准 5.19/3 指出“整数常量表达式是整数或无范围枚举类型的表达式,隐式转换为纯右值,其中转换后的表达式是核心不变的表达。”从 5.3.3/6 和 18.2/6 你可以推断它是一个prvalue。
E1 > E2 :标准 5.8.1 “操作数应为整数或非范围枚举类型,并执行整数提升。结果的类型是提升的左操作数的类型。”。根据标准 4.5 和特别是 pt.7,积分提升意味着prvalue。
& |和 ^ :标准指定“执行通常的算术转换; (...) 运算符仅适用于整数或无范围枚举操作数”。
【讨论】:
E1 被提升,而不是我们正在查看的表达式 E1 << E2 或 E1 >> E2。并且表达式可以具有提升操作数的类型而不是纯右值;类型和值类别大多是正交的。
有一些小东西: http://rextester.com/DUEJY28518:
std::cout << typeid(decltype(sizeof(char))).name() << std::endl;
std::cout << typeid(decltype(1 << 1)).name() << std::endl;
std::cout << typeid(decltype(1 >> 1)).name() << std::endl;
std::cout << typeid(decltype(~1)).name() << std::endl;
std::cout << typeid(decltype(1 & 1)).name() << std::endl;
std::cout << typeid(decltype(1 | 1)).name() << std::endl;
std::cout << typeid(decltype(1 ^ 1)).name() << std::endl;
std::cout << "-------------" << std::endl;
std::cout << typeid(decltype(1U << 1)).name() << std::endl;
std::cout << typeid(decltype(1U >> 1)).name() << std::endl;
std::cout << typeid(decltype(~1U)).name() << std::endl;
std::cout << typeid(decltype(1U & 1)).name() << std::endl;
std::cout << typeid(decltype(1U | 1)).name() << std::endl;
std::cout << typeid(decltype(1U ^ 1)).name() << std::endl;
std::cout << "-------------" << std::endl;
std::cout << typeid(decltype(1L << 1)).name() << std::endl;
std::cout << typeid(decltype(1L >> 1)).name() << std::endl;
std::cout << typeid(decltype(~1L)).name() << std::endl;
std::cout << typeid(decltype(1L & 1)).name() << std::endl;
std::cout << typeid(decltype(1L | 1)).name() << std::endl;
std::cout << typeid(decltype(1L ^ 1)).name() << std::endl;
结果是(MSVC):
unsigned int
int
int
int
int
int
int
-------------
unsigned int
unsigned int
unsigned int
unsigned int
unsigned int
unsigned int
-------------
long
long
long
long
long
long
【讨论】: