此代码在 GCC 和 Clang 中有效,但在 MSVC 上无效:
#include <concepts>
#include <utility>
struct S {};
const S&& f();
S g();
static_assert(std::same_as<decltype(false ? f() : g()), const S>);
https://godbolt.org/z/99rMPzecM
MSVC认为decltype(false ? f() : g())
是const S&&
哪一个是正确的?为什么?
根据我的理解,所有编译器都是错误的,程序应该是格式错误的。
相关工作
有一个已提交但尚未编号的CWG 问题,其中@BrianBi解释了这种情况,并得出结论认为该措辞暗示格式不正确。
另一方面,@Barry在P3177R0 的条件运算符中编译了这些类型组合,并声称
const S
将生成这些组合。目前还不清楚这是否正确以及作者如何得出这个结论。语言律师
要最终确定正确的类型,请考虑确定[expr.cond]中条件运算符类型的规则。除此之外,在 中
... ? f() : g()
,编译器尝试将 和 转换f()
为g()
,g()
如f()
[ expr.cond] p4中所述。第 1 步:确定目标类型
为了上述转换的目的,编译器确定两个隐式转换序列的目标类型。
注意:
f()
类型为,在任何分析之前const S&&
都会变成 xvalue 类型( [expr.type] p1 ),并且const S
g()
是 类型的纯右值S
。转换为
f()
相关类型S
此转换根据[expr.cond] p4.3进行:
g()
是纯右值,所以这是相关的情况。由于
const S
和S
是相同的类类型(忽略 cv 限定),但S
cv 限定较少,因此[expr.cond] p4.3.1不适用,而是将左值到右值转换应用于 E2,即g()
([expr.cond] p4.3.3 ):结果:目标类型为
S
注意:列出的转换
E2
均不g()
适用,但是应该没问题。由于您显然无法应用所有三个列出的转换,因此措辞的目的是应用这些转换中的任何一个(如果可能)。换句话说,目标类型只是E2
没有应用任何内容。转换为
g()
相关类型const S
- [expr.cond] p4.2
f()
假设这是一个 xvalue,并且引用const S&&
可以直接绑定到类型S
( [dcl.init.ref] p5.3.1 )的纯右值,则满足此处的所有条件。结果:目标类型是“右值引用
const S
”第 2 步:隐式转换
现在,编译器检查是否可以使用给定的目标类型执行隐式转换。
f()
可以转换为S
使用隐式定义的(简单的)复制构造函数,该构造函数将作为用户定义的转换序列的一部分进行调用,并且g()
const S&&
可以通过简单地绑定引用来转换。我们现在应该遇到[expr.cond] p4,第 5 句:
编译器此时应该拒绝该代码,但是没有人这样做。
GCC/clang 视角
如果我们继续假设程序格式不正确,我们将遇到[expr.cond] p6:
结果是纯右值,我们应用最后一轮左值到右值转换来进行 make
g()
(因为它已转换为右值引用)。显然,GCC 认为这个纯右值应该是 类型const S
,但我们不应该走到这一步。由于结果是纯右值并且
decltype
不会应用于无括号的 id 表达式,因此decltype
应生成S
orconst S
( [dcl.type.decltype] p1.6 )。MSVC 的观点
MSVC 可能认为两个转换之一失败,在这种情况下结果可能是
const S&&
:- [expr.cond] p5
但是,尚不清楚为什么 MSVC 认为
f()
不能转换为S
,这会导致两个操作数const S&&
现在都是 xvalues 类型的情况。