三元运算符求值顺序
Posted
技术标签:
【中文标题】三元运算符求值顺序【英文标题】:Ternary operator evaluation order 【发布时间】:2010-11-29 12:55:05 【问题描述】:class Foo
public:
explicit Foo(double item) : x(item)
operator double() return x*2.0;
private:
double x;
double TernaryTest(Foo& item)
return some_condition ? item : 0;
Foo abc(3.05);
double test = TernaryTest(abc);
在上面的例子中,如果 some_condition 为真,为什么 test 等于 6(而不是 6.1)?
如下更改代码返回值 6.1
double TernaryTest(Foo& item)
return some_condition ? item : 0.0; // note the change from 0 to 0.0
似乎(在原始示例中)来自 Foo::operator double 的返回值被强制转换为 int,然后返回为 double。为什么?
【问题讨论】:
它与条件运算符无关。这也打印 6。不过,我不知道为什么。 #include%.f
打印 0 个小数位。
看起来反转条件也解决了问题 - 返回 some_condition ? 0:项目;
【参考方案1】:
条件运算符检查两个方向的转换。在这种情况下,由于您的构造函数是显式的(因此?:
并不模棱两可),因此使用从Foo
到int
的转换,使用转换为double
的转换函数:这行得通,因为在应用之后转换函数,将double
转换为int
(截断)的标准转换如下。在您的情况下,?:
的结果是int
,其值为6
。
在第二种情况下,由于操作数的类型为double
,因此不会发生到int
的这种尾随转换,因此?:
的结果类型具有具有预期值的double
类型。
要了解“不必要的”转换,您必须了解像 ?:
这样的表达式是“无上下文”评估的:在确定它的值和类型时,编译器不会认为它是return
用于返回 double
的函数。
编辑:如果你的构造函数是隐式会发生什么? ?:
表达式将是不明确的,因为您可以将int
转换为Foo
类型的右值(使用构造函数),并将Foo
转换为int
类型的右值(使用转换函数) .标准说
使用这个过程,判断第二个操作数是否可以转换为匹配第三个操作数,以及第三个操作数是否可以转换为匹配第二个操作数。如果两者都可以转换,或者一个可以转换但转换不明确,则程序格式错误。
解释您的Foo
如何转换为int
的段落:
5.16/3
关于condition ? E1 : E2
:
否则,如果第二个和第三个操作数具有不同的类型,并且其中一个具有(可能是 cv 限定的)类类型,则会尝试将这些操作数中的每一个转换为另一个的类型。 [...] 如果 E1 可以隐式转换为表达式 E2 将具有的类型(如果 E2 转换为右值,则 E1 可以转换为匹配 E2)(或者它具有的类型,如果 E2 是右值)。
4.3
关于“隐式转换”:
表达式 e 可以隐式转换为类型 T 当且仅当声明
T t = e;
对于某些发明的临时变量 t 格式正确。
8.5/14
关于拷贝初始化(T t = e;
)
如果源类型是(可能是 cv 限定的)类类型,则考虑转换函数。列举了适用的转换函数 (13.3.1.5),并通过重载决议 (13.3) 选择最佳转换函数。调用如此选择的用户定义转换将初始化表达式转换为正在初始化的对象。如果转换无法完成或不明确,则初始化格式错误。
13.3.1.5
关于候选转换函数
考虑了 S 及其基类的转换函数。那些没有隐藏在 S 中并产生类型 T 或可以通过标准转换序列 (13.3.3.1.1) 转换为类型 T 的类型是候选函数。
【讨论】:
在不隐含 Foo 构造函数的情况下有什么方法可以解决这个问题? 当然——明确地使类型相同。如您所见,将数字文字转换为双精度值可以使其按您想要的方式工作。 我的意思是在快速浏览代码后截断不是很明显。拥有一个隐式的 Foo 构造函数可以解决这个问题。那么 0.0 和 return condition 也一样吗?项目 : Foo(0); 隐式构造函数会使?:
表达式不明确。为什么不直接写0.0
?
@litb:没有。使构造函数隐式意味着 ?: 计算为 Foo,因为 int 可以通过 Foo(double) 进行转换【参考方案2】:
这在标准的第 5.16 节中有令人困惑的细节。重要的部分在第 3 段中。“如果 E2 是左值:如果 E1 可以隐式转换(第 4 条)为类型“对 T2 的引用”,则可以将 E1 转换为匹配 E2,但要遵守在转换中引用必须直接绑定 (8.5.3) 到 E1。”
在表达式中,唯一的左值是item
,所以问题是0(一个int)是否可以隐式转换为Foo
类型。在这种情况下,没有任何其他类型到Foo
的隐式转换,因为唯一可用的转换函数被标记为explicit
。因此,这是行不通的,我们接着是“如果 E2 是右值,或者如果上面的转换无法完成:”(跳过关于它们是否都具有类类型的部分)“否则(即,如果 E1 或 E2具有非类类型,或者如果它们都具有类类型但基础类不同或不是另一个的基类):如果 E1 可以隐式转换为表达式 E2 的类型,则 E1 可以转换为匹配 E1如果 E2 被转换为一个右值(或它所具有的类型,如果 E2 是一个右值),将会有。"
因此,我们看到 0 是一个右值,类型为 int
。我们可以转换Foo
,因为我们可以将Foo
隐式转换为double
,然后再转换为int
。那么:
"使用这个过程,判断第二个操作数是否可以转换为匹配第三个操作数,以及第三个操作数是否可以转换为匹配第二个操作数。如果两者都可以转换,或者一个可以转换但转换不明确,程序格式错误。如果两者都不能转换,则操作数保持不变,并按如下所述执行进一步检查。如果恰好可以进行一次转换,则将该转换应用于所选操作数和转换后的操作数在本节的其余部分中,操作数用于代替原始操作数。"
由于我们可以将Foo
转换为int
,因此我们将Foo
转换为int
以进行剩余的确定。我们现在有两个int
s 作为表达式类型,并且至少有一个是右值。
我可以继续第 5 和第 6 段,但我认为表达式的类型很明显 int
。
我认为要点是:
您的编译器按照标准运行。
条件表达式类型的规则太复杂,不容易学习。不要勉强信封,因为有时你会犯错误。 (此外,这正是编译器可能无法准确实现标准的地方。)
尝试指定类型,以便第二个和第三个表达式的类型相同。在任何情况下,尽量避免使用不是所需类型的表达式。
【讨论】:
谢谢。这就是我所追求的那种细节。 第一个块,它需要从E1
转换为 reference to T2
不适用 - 因为 Foo &t = 10;
永远不会工作,即使使用 Foo
的隐式构造函数。此外,约束是引用直接绑定到E1
。这仅适用于由转换函数 operator Foo&();
转换为 reference to Foo
的 E1
(参见 8.5.3/5)。
@litb:感谢您的澄清。【参考方案3】:
三元运算符从其参数中猜测类型。它无法将 item 转换为 int,但它可以将 item 转换为 double,然后将其转换为 int。
【讨论】:
【参考方案4】:三元表达式的类型在编译时确定; some_condition 在运行时是什么并不重要。
我想问题是:为什么编译器在第一个示例中选择 int 而不是 double ?
【讨论】:
确实,条件无关紧要。感谢您澄清问题。以上是关于三元运算符求值顺序的主要内容,如果未能解决你的问题,请参考以下文章