具有相同底层 class 类型的条件运算符

Conditional operator with same underlying class type

这个程序应该输出0还是1?在我阅读和理解 C++14 标准中引用的段落时,它应该打印 1,但 GCC 和 clang 都打印 0(因为推导的类型是 A const 而不是 A const&):

#include <iostream>

struct A {};

int main()
{
    A a;
    A const& ra = std::move(a); // #1

    std::cout << std::is_same<decltype(true ? ra : std::move(a)),
                              A const&>::value; // Prints 0
}

在这种情况下,ra 是一个 A const 左值,std::move(a) 是一个 A 左值,都是 class 类型。根据条件运算符的标准(重点是我的),结果应该是 A const 类型的 lvalue,因此 decltype 结果必须是 A const&:

[expr.cond]/3 Otherwise, if the second and third operand have different types and either has (possibly cv-qualified) class type, or if both are glvalues of the same value category and the same type except for cv-qualification, an attempt is made to convert each of those operands to the type of the other. The process for determining whether an operand expression E1 of type T1 can be converted to match an operand expression E2 of type T2 is defined as follows:

— If E2 is an lvalue: E1 can be converted to match E2 if E1 can be implicitly converted (Clause 4) to the type “lvalue reference to T2”, subject to the constraint that in the conversion the reference must bind directly (8.5.3) to an lvalue.

[...]

此时E2就是ra,是一个左值,另一个可以隐式转换为"lvalue reference to T2" ,如行// #1所示。 "lvalue reference to T2"被翻译为A const&,所以,std::move(a)直接绑定到A const类型的左值,转换后,两者操作数具有相同的类型和值类别,因此:

[expr.cond]/3 If the second and third operands are glvalues of the same value category and have the same type, the result is of that type and value category [...].

所以,运算符结果应该是一个左值,decltype结果应该是一个引用,因此程序应该打印 1.

这个问题措辞笨拙。相反,您应该询问表达式 true ? ra : std::move(a) 的类型和值类别应该是什么。该问题的答案是 A const 类型的纯右值。这随后意味着程序应该打印 0,正如我认为每个编译器都正确地做的那样。


?: 的规则相当复杂。在这种情况下,我们有两个 class 类型的表达式,我们尝试根据有限的规则子集查看是否可以相互转换。

尝试转换 rastd::move(a) 失败。我们首先尝试使用 target type is A&& which can't bind directly to ra. We then try the backup plan in (3.3.1),因为这两个表达式具有相同的基础 class 类型,但我们的目标表达式至少没有源表达式那样的 cv 限定,所以这也失败了。

尝试转换 std::move(a)ra 失败 (3.1) because we need to bind directly to an lvalue (we can bind an rvalue to a const lvalue reference, but here we are required to bind an lvalue). But, the (3.3.1) 备份成功,因为现在目标类型 至少与 cv-qualified 一样来源。

因此,我们应用转换并继续,就好像第二个操作数是类型 A const 的左值,但第三个操作数现在是类型 A const 的纯右值(而不是 xvalue 的输入 A).

(4) 失败,因为它们不属于同一值类别。

因此,result is a prvalue. And since they have the same type, the result is of that typeA const