条件运算符的值类别

Nat*_*ell 4 c++ g++ conditional-operator language-lawyer value-categories

请考虑以下代码:

int x;
int& f() {
  return x ? x : throw 0;
}
Run Code Online (Sandbox Code Playgroud)

随着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’
Run Code Online (Sandbox Code Playgroud)

请注意,这在clang中编译得很好.以下是(我相信的)标准中的相关陈述:

N4659 [8.16.2.1](条件运算符):
第二个或第三个操作数(但不是两个)是一个(可能带括号的)throw-expression(8.17); 结果是另一个的类型和值类别.

据我所知,x是一个左值,所以在我看来clang是对的.我错了吗?


如果我不得不猜测,那么l-to-rvalue转换正在发生,因为条件中的两个表达式没有相同的类型,但因为第二个是抛出,所以应该抢占此转换.我不熟悉提交错误报告,但也许这将是一个更好的论坛.
以下是关于条件运算符的一些(可能)更有用的问题:
为什么此函数在给定rvalue参数的情况下返回左值引用?
错误:这个简单的C代码需要左值?(三元有任务吗?)

Sha*_*our 6

clang在这里是正确的,旧的行为是无条件地将值转换为它看起来像gcc仍然实现的prvalue.

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

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

如果第二个或第三个操作数的类型为void,则是左值到右值(7.1 [conv.lval]),数组到指针(7.2 [conv.array])和函数到指针(7.3) [conv.func])对第二个和第三个操作数执行标准转换,并且以下之一应该成立:

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

这似乎是无偿和令人惊讶的.

并将[expr.cond]中DR 1550的措辞改为现在的内容:

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

因此,当clang正在实施DR时,gcc似乎正在实现旧行为.

这是将DR 1560应用于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;
}
Run Code Online (Sandbox Code Playgroud)

对godbolt我们可以看到失败gcc的原因是:

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

我们有一个非常类似的问题的gcc 错误报告,其中包含以下简化的测试用例:

我想突破这个bug并提供一个更简单的测试用例:

void blah(int&) {}

int main() {
    int i{};
    blah(true ? i : throw);
}
Run Code Online (Sandbox Code Playgroud)

结果与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);
          ~~~~~^~~~~~~~~~~~~
Run Code Online (Sandbox Code Playgroud)