'?:'(三元/条件运算符)的结果类型是什么?

216
为什么第一个条件运算符会返回一个引用?
int x = 1;
int y = 2;
(x > y ? x : y) = 100;

然而,第二个不。
int x = 1;
long y = 2;
(x > y ? x : y) = 100;

实际上,第二个根本无法编译。
error: lvalue required as left operand of assignment
      |     (x > y ? x : y) = 100;
      |     ~~~~~~~^~~~~~~~

1
嗯,这就像是为烤面包找到一个特殊情况,我以前没有遇到过。 - Ulterior
4
C/C++三元运算符——一些有趣的观察 - bartolo-otrit
1
由于将类型分配给表达式将意味着至少一个术语的强制转换,因此该术语将不再是左值。 - user1196549
4个回答

180

表达式没有返回类型,它们有一种类型和——如在最新的C++标准中所知——一个值类别。

条件表达式可以是左值右值。这是它的值类别。(这有点简化,在C++11中我们有左值、x值和pr值。)

广义而言,左值指的是内存中的对象,而右值只是一个值,不一定与内存中的对象相关联。

赋值表达式将一个值赋给一个对象,因此被赋值的对象必须是左值

对于一个条件表达式(?:)来说,如果它要成为左值(同样是在广义和简单的术语下),第二个和第三个操作数必须是相同类型的左值。这是因为条件表达式的类型和值类别是在编译时确定的,必须是适当的,无论条件是否为真。如果其中一个操作数必须转换为另一种类型以匹配另一个操作数,则条件表达式不能是左值,因为这种转换的结果不是左值

ISO/IEC 14882:2011 参考:

3.10 [basic.lval] Lvalues and rvalues(关于值类别)

5.15 [expr.cond] Conditional operator(条件表达式的类型和值类别规则)

5.17 [expr.ass] Assignment and compound assignment operators(赋值的左手边必须是可修改的左值的要求)


4
当我在阅读有关xvalue和prvalue的内容时(因为在你的回答之前我从未听说过它们),我发现了这篇有用的SO帖子:https://dev59.com/QnA65IYBdhLWcg3w1SfU - fluffy
@SoulReaper:prvalue,xvalue,glvalue是值类别。 - Xeo
@Xeo,我很感激你的帮助,但你能告诉我他的意思是什么吗?“rvalue只是一个值,不一定附加在内存中的对象上。”有例子吗? - Mr.Anubis
@SoulReaper:我认为他在谈论像truethisenum值之类的东西。这些东西是prvalue(“纯”rvalue),但并不存储在内存中。 - Xeo
我认为这只是与xvalue形成对比。我相信一个rvalue变量会保留它所给定的xvalue,并且返回rvalue(例如使用move(value))将恢复其过期。区别在于,xvalue会随着当前表达式的过期而过期,因为它是匿名的,并且在表达式被丢弃后无法引用,除非它被分配给一个变量;rvalue变量引用该值,因此只要变量在作用域内,它就不会过期;所讨论的对象是局部于该范围的。这样可以吗?我真的希望如此。 - John P
显示剩余2条评论

61

?: 表达式的类型是其第二个和第三个参数的共同类型。如果两种类型相同,则返回一个引用。如果它们可以相互转换,那么其中一个被选择并转换为另一个(在这种情况下提升)。由于不能返回对临时变量(已转换/提升变量)的左值引用,因此其类型是一个值类型。


1
确实是个很好且快速的回答!:),但如果您提供任何参考资料(不是C++的参考资料,而是网络上的参考资料:D),我会更愉快。 - Tamer Shlash
1
@Mr.TAMER:我宁愿深入研究标准。 :< - Xeo
3
@Yola:由于类型是C++中的编译时概念,因此表达式的实际返回值并不重要。 - Xeo
1
你不会得到一个引用,而是会得到一个左值。 - Suma
1
@Xeo:虽然不是C++术语;) - Sebastian Mach
显示剩余5条评论

19

由于需要将x的类型隐式提升以匹配y的类型(因为:两侧的类型不同),所以它无法返回一个左值,并且必须创建一个临时变量。


标准规定是什么?(n1905)

表达式 5.17 赋值和复合赋值运算符

5.17/3

如果第二个和第三个操作数具有不同的类型,并且其中任意一个具有(可能带有cv限定符的)类类型,则尝试将这些操作数转换为另一个操作数的类型。确定类型为T1的操作数表达式E1是否可以转换为类型为T2的操作数表达式E2的过程定义如下:

— 如果E2是左值:如果E1可以隐式转换(第4条)为类型“T2的引用”,则可以将E1转换为与E2匹配,但在转换中,引用必须直接绑定(8.5.3)到E1。

— 如果E2是右值,或者上述转换无法完成:

- 如果 E1 和 E2 均为类类型,且底层类类型相同或者其中一个是另一个的基类,则当 T2 的类与 T1 的类相同或者是 T1 的基类时,E1 可以转换为匹配 E2,同时 T2 的 cv-限定符必须与 T1 的 cv-限定符相同或者更多。如果应用了转换,则通过从 E1 复制初始化临时对象 T2 并使用该临时对象作为转换操作数,将 E1 改变为 T2 类型的 rvalue,但该 rvalue 仍指向原始源类对象(或其相应的子对象)。[注意:也就是说,不会进行复制。- end note] - 否则(即,如果 E1 或 E2 其中之一具有非类类型,或者如果它们都具有类类型,但底层类不是相同的或者一个不是另一个的基类),则当 E1 可以隐式转换为表达式 E2 被转换为 rvalue 的类型(或者如果 E2 是 rvalue,则为其本身类型)时,E1 可以转换为匹配 E2。 - 使用此过程,确定第二个操作数是否可以转换为匹配第三个操作数,并确定第三个操作数是否可以转换为匹配第二个操作数。如果两个操作数都可以转换,或者一个可以转换但转换是模糊的,则程序是非法的。如果两个都无法转换,则操作数保持不变,并执行下面描述的进一步检查。如果只有一个转换是可能的,则将该转换应用于所选操作数,并在本节的其余部分中使用转换后的操作数代替原始操作数。
- 如果第二个和第三个操作数均为 lvalue 且类型相同,则结果为该类型,并且它是一个 lvalue,如果第二个或第三个操作数是位域,或者两个都是位域,则结果也是位域。否则,结果就是一个右值。如果第二个和第三个操作数的类型不相同,并且其中任意一个具有(可能带有cv限定符的)类类型,则会使用重载决议来确定要应用于操作数的转换(如果有)。 如果重载解析失败,则程序无效。否则,因此确定的转换被应用,并且所转换的操作数将用于本节的其余部分,代替原始操作数。

0

再举一个例子

    int  x = 1;
    int  y = 2;
    long z = 3;

    (true ?  x : y) = 100; // x & y are SAME type so it returns Lvalue(so it can be LHS of =). x = 100, y = 2
    (false ? x : y) = 100; // x & y are SAME type so it returns Lvalue(so it can be LHS of =). x = 1  , y = 100

    // (true  ? x : z) = 100; // Error: x & z are DIFFERENT types so it returns Rvalue (so it can NOT be LHS of =)
    // (false ? x : z) = 100; // Error: x & z are DIFFERENT types so it returns Rvalue (so it can NOT be LHS of =)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接