条件运算符的值类别

时间:2018-11-15 21:52:42

标签: c++ g++ language-lawyer conditional-operator value-categories

考虑以下代码:

int x;
int& f() {
  return x ? x : throw 0;
}

使用gcc version 7.3.0 (Ubuntu 7.3.0-27ubuntu1~18.04)时出现以下编译错误:

cannot bind non-const lvalue reference of type ‘int&’ to an rvalue of type ‘int’

请注意,这可以在clang中正常编译。这是(我相信是)该标准的相关声明:

  

N4659 [8.16.2.1](视情况而定):
  第二个或第三个操作数(但不是两个)都是(可能带有括号的)throw-expression(8.17);结果是另一个的类型和值类别。

据我了解,x是左值,所以在我看来clang是正确的。我错了吗?


如果我不得不猜测,则会发生从左到右的转换,因为条件中的两个表达式的类型不同,但是由于第二个表达式是throw,因此应优先进行此转换。我不熟悉提交错误报告,但是也许这会是一个更好的论坛。
以下是有关条件运算符的一些(可能)更有用的问题:
Why does this function return an lvalue reference given rvalue arguments?
Error: lvalue required in this simple C code? (Ternary with assignment?)

1 个答案:

答案 0 :(得分:6)

这里的clang是正确的,以前的行为是将值无条件地转换为 prvalue ,看起来gcc仍在实现。

这是DR 1560的主题,它由DR 1550的分辨率确定。 DR 1560说:

  

一个glvalue出现为in中条件表达式的一个操作数   另一个操作数是throw-expression的结果将转换为a   prvalue,无论如何使用条件表达式:

     
    

如果第二个或第三个操作数的类型为void,则从左值到右值(7.1 [conv.lval]),从数组到指针(7.2     [conv.array])和函数指针(7.3 [conv.func])标准     转换是在第二和第三操作数上执行的,其中之一     应当满足以下条件:

         
        
  • 第二个或第三个操作数(但不是两个)都是throw-expression(18.1 [except.throw]);结果是以下类型的     另一个是prvalue。
  •     
  
     

这似乎是免费的,令人惊讶。

DR 1550[expr.cond]中的措词更改为现在的内容:

  

第二个或第三个操作数(但不是两个)都是(可能带括号的)throw-expression;结果是另一个的类型和值类别。如果该操作数是位字段,则条件表达式是位字段。

因此,当clang实现DR时,gcc似乎实现了旧的行为。

这是patch that applied DR 1560 to clang。它添加了以下测试:

namespace DR1560 { // dr1560: 3.5
  void f(bool b, int n) {
    (b ? throw 0 : n) = (b ? n : throw 0) = 0;
  }
  class X { X(const X&); };
  const X &get();
  const X &x = true ? get() : throw 0;
}

on godbolt we can see this fails for gcc的原因是

error: lvalue required as left operand of assignment
4 |     (b ? throw 0 : n) = (b ? n : throw 0) = 0;
  |                    

我们有一个gcc bug report for a very similar issue,它具有以下简化的测试用例:

  

我想解决这个错误并提供一个更简单的测试用例:

void blah(int&) {}

int main() {
    int i{};
    blah(true ? i : throw);
}
     

使用gcc 6.0的结果:

prog.cc: In function 'int main()':
prog.cc:6:15: error: invalid initialization of non-const reference of type 'int&' from an rvalue of type 'int'
     blah(true ? i : throw 0);
          ~~~~~^~~~~~~~~~~~~