考虑以下代码:
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
是正确的。我错了吗?
答案 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); ~~~~~^~~~~~~~~~~~~