【问题标题】:What is the value category of result yielded from shift operators, bit-wise operators, and sizeof operator?移位运算符、位运算符和 sizeof 运算符产生的结果的值类别是什么?
【发布时间】:2014-08-22 12:00:45
【问题描述】:

移位运算符:<< >> 位运算符:~&^| sizeof 运算符:sizeof()

根据 C++ 标准 (n3797),我只能确认 ~ 产生纯右值 (5.3.1/2),而不是上面的其他值。

【问题讨论】:

  • decltype(sizeof(char))
  • [expr.sizeof]/6 "sizeofsizeof... 的结果是 std::size_t 类型的常量。"但我不确定 constant 是否严格暗示 prvalue
  • @dyp 我想说如果没有指定它是prvalue,类似于this
  • @user2451677,我指的是rvalue、lvalue、xvalue,而不是变量类型(例如int、double)
  • @dyp 讨论 here 有 Jens 的类似提议,即算术转换应该隐含 l 到 r 的转换。

标签: c++ operators expression language-lawyer


【解决方案1】:

据我所知,结果是 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 - 但我认为说促销旨在为所有操作数产生纯右值并不是太牵强。跨度>
  • 事实上,正如 aschepler 在我自己的回答中指出的那样,对操作数执行积分提升这一事实不足以保证 operator
  • @dyp 我更新了我的答案,如果你认为这听起来合理或者我犯了什么错误,请告诉我。
  • Howard Hinnant 对 Luc 的 here 有不同的处理方式,读起来很有趣。
  • 有趣的是,对于x += 1x 被转换为重写形式 x = x + 1 的纯右值,但运算符仍然产生一个左值。同样,x++。我认为基本思想是算术和位运算符产生 与两个操作数不同的值。因此,将任一操作数作为左值返回是没有意义的。您可以使用赋值或显式构造(命名实体的;也:函数参数)创建具有“新”值的对象。
【解决方案2】:

cppreferencefunctions 说如下:
prvaule(“纯”右值)是一个标识临时对象(或其子对象)的表达式或者是一个不与任何对象关联的值。
以下表达式是纯右值:

  1. 文字(字符串文字除外),例如 42 或 true 或 nullptr。
  2. 函数或运算符的返回类型不是引用时的函数调用/运算符表达式,例如 str.substr(1, 2) 或 2+2

这适用于函数,我不确定关于内置运算符的具体规则是什么。

【讨论】:

  • 赋值也不会“返回一个引用”,而是产生一个左值。这些内置运算符都不是函数。
  • @eladm26,增量运算符 ++(前缀)确实返回一个左值,这似乎与此相矛盾
  • @dyp 我明白了,我的错误,我混合了函数和运算符。
【解决方案3】:

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 &lt;&lt; E2E1 &gt;&gt; E2。并且表达式可以具有提升操作数的类型而不是纯右值;类型和值类别大多是正交的。
  • @aschepler 是否有产生左值的标准转换? (身份转换除外)
  • @dyp 不在第 4 节中,尽管 13.3.3.1.4 将一些引用绑定视为“转换”。为什么?
  • @dyp 你是对的,但是 aschepler 的意思是操作符可能以产生 xvalue 的方式实现,例如通过使用引用临时对象的表达式。虽然我倾向于将 xvalue 理解为需要具有显式引用的表达式(3.10 和 8.3.2),并且我希望 CPU 移位指令作为没有中间引用的纯右值,但这里显然存在不确定性。
【解决方案4】:

有一些小东西: 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

【讨论】:

  • 当你,@rabbit 挖洞者,编辑问题的标题时,你改变了问题。非常有趣,不是吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-09-21
  • 1970-01-01
  • 1970-01-01
  • 2014-03-21
相关资源
最近更新 更多