【问题标题】:Return type of '?:' (ternary conditional operator)'?:' 的返回类型(三元条件运算符)
【发布时间】:2026-02-24 17:35:01
【问题描述】:

为什么第一个返回引用?

int x = 1;
int y = 2;
(x > y ? x : y) = 100;

而第二个没有?

int x = 1;
long y = 2;
(x > y ? x : y) = 100;

实际上,第二个根本没有编译——“not lvalue left of assignment”。

【问题讨论】:

标签: c++ types reference conditional-operator lvalue


【解决方案1】:

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

条件表达式可以是左值右值。这是它的价值范畴。 (这有点简化,在C++11 我们有左值、xvalue 和prvalues。)

在非常广泛和简单的术语中,lvalue 是指内存中的对象,而 rvalue 只是一个可能不一定附加到内存中的对象的值.

赋值表达式为一个对象赋值,所以被赋值的东西必须是一个左值

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

ISO/IEC 14882:2011 参考:

3.10 [basic.lval] 左值和右值(关于值类别)

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

5.17 [expr.ass] 赋值和复合赋值运算符(要求赋值的 l.h.s. 必须是可修改的左值)

【讨论】:

  • 在阅读 xvalue 和 prvalue 时(因为在您回答之前我没有听说过它们)我遇到了这个方便的 SO 帖子:*.com/questions/3601602/…
  • an rvalue is just a value that may not necessarily be *attached* to an object in memory. 你能用更简单的术语解释一下吗? .另外,type and value *category* 是什么意思?谢谢
  • @SoulReaper: prvalue, xvalue, glvalue 是价值类别。
  • @Xeo 非常感谢您的帮助,但您能说出他所说的右值只是一个可能不一定附加到内存中的对象的值。 吗?举个例子?
  • @SoulReaper:我认为他在谈论诸如truethisenum 值之类的东西。这些东西是纯右值(“纯”右值),但并不存在于内存中。
【解决方案2】:

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

【讨论】:

  • 确实很好,很快的回答! :),但如果您对此提供任何参考(不是 C++ 参考,而是网络参考 :D),我会更愉快。
  • @Mr.TAMER:我宁愿深入研究标准。 :
  • @Yola:由于类型是 C++ 中的编译时概念,因此表达式的实际返回 value 并不重要。
  • 你没有得到引用,你得到的是左值。
  • @Xeo:虽然不是 C++ 术语 ;)
【解决方案3】:

它不能返回 左值,因为它必须隐式提升 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 的类与 E1 的类型相同,则 E1 可以转换为匹配 E2,或者T1 的基类、T1 的类和 T2 的 cv 限定与 T1 的 cv 限定相同或更高的 cv 限定。如果应用了转换,则 E1 将更改为 T2 类型的右值,该右值仍引用原始源类对象(或其适当的子对象)。 [注意:也就是说,没有复制。 — 结束注释] 通过从 E1 复制初始化 T2 类型的临时并将该临时用作转换后的操作数。

否则(即,如果E1 或 E2 具有非类类型,或者如果它们都具有类类型但基础类不同或不是另一个的基类):E1 可以转换为如果 E1 可以隐式转换为表达式 E2 在 E2 转换为右值时所具有的类型(或它具有的类型,如果 E2 是一个右值),则匹配 E2。

使用这个过程,判断第二个操作数是否可以转换为匹配第三个操作数,以及第三个操作数是否可以转换为匹配第二个操作数。如果两者都可以转换,或者一个可以转换但转换不明确,则程序格式错误。如果两者都不能转换,则操作数保持不变,并按如下所述执行进一步检查。如果恰好可以进行一次转换,则该转换将应用于所选操作数,并在本节的其余部分中使用转换后的操作数代替原始操作数。


5.17/4

如果第二个和第三个操作数是左值并且具有相同的类型,则结果是该类型并且是一个左值,如果第二个或第三个操作数是位域,或者两者都是位域,则结果是位域是位域。


5.17/5

否则,结果为右值。如果第二个和第三个操作数的类型不同,并且都具有(可能是 cv 限定的)类类型,则使用重载决议来确定要应用于操作数(13.3.1.2、13.6)的转换(如果有) .如果重载决议失败,则程序格式错误。否则,将应用由此确定的转换,并在本节的其余部分中使用转换后的操作数代替原始操作数。

【讨论】: