三元运算符求值顺序

发布于 2024-08-06 00:21:56 字数 570 浏览 6 评论 0原文

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 为 true,为什么 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。为什么?

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);

In the above example, why is test equal to 6 (instead of 6.1) if some_condition is true?

Changing the code like below returns value of 6.1

double TernaryTest(Foo& item) {
  return some_condition ? item : 0.0; // note the change from 0 to 0.0
}

It seems that (in the original example) the return value from Foo::operator double is cast to an int and then back to a double. Why?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

橘香 2024-08-13 00:21:56

条件运算符检查两个方向的转换。在这种情况下,由于您的构造函数是显式的(因此 ?: 没有歧义),因此使用从 Fooint 的转换,使用转换为 double 的转换函数:这是可行的,因为在应用转换函数之后,标准转换将 double 转换为 int (截断)如下。在您的情况下, ?: 的结果是 int,并且值为 6

在第二种情况下,由于操作数的类型为 double,因此不会发生到 int 的尾随转换,因此结果类型为 ?:类型 double 具有预期值。

要理解“不必要的”转换,您必须了解像 ?: 这样的表达式是“与上下文无关”评估的:在确定它的值和类型时,编译器不会认为它是返回 double 的函数的 return 操作数。


编辑:如果您的构造函数是隐式的,会发生什么情况? ?: 表达式将是不明确的,因为您可以将 int 转换为 Foo 类型的右值(使用构造函数),并且将 < code>Foo 转换为 int 类型的右值(使用转换函数)。标准说

利用该过程,确定是否可以将第二操作数转换为与第三操作数匹配,以及是否可以将第三操作数转换为与第二操作数匹配。如果两者都可以转换,或者其中之一可以转换但转换不明确,则程序格式错误。


解释如何将 Foo 转换为 int 的段落:

5.16/3 关于 condition ? E1:E2:

否则,如果第二个和第三个操作数具有不同的类型,并且其中一个具有(可能是 cv 限定的)类类型,则会尝试将其中每个操作数转换为另一个操作数的类型。 [...] 如果 E1 可以隐式转换为表达式 E2 在 E2 转换为右值时将具有的类型(或者它所具有的类型,如果 E2 是右值),则 E1 可以转换为匹配 E2。

4.3 关于“隐式转换”:

对于某些发明的临时变量 t,当且仅当声明 T t = e; 格式良好时,表达式 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 的类型是候选函数。

The conditional operator checks conversions in both directions. In this case, since your constructor is explicit (so the ?: is not ambiguous), the conversion from Foo to int is used, using your conversion function that converts to double: That works, because after applying the conversion function, a standard conversion that converts the double to int (truncation) follows. The result of ?: in your case is int, and has the value 6.

In the second case, since the operand has type double, no such trailing conversion to int takes place, and thus the result type of ?: has type double with the expected value.

To understand the "unnecessary" conversions, you have to understand that expressions like your ?: are evaluated "context-free": When determining the value and type of it, the compiler doesn't consider that it's the operand of a return for a function returning a double.


Edit: What happens if your constructor is implicit? The ?: expression will be ambiguous, because you can convert an int to an rvalue of type Foo (using the constructor), and a Foo to an rvalue of type int (using the conversion function). The Standard says

Using this process, it is determined whether the second operand can be converted to match the third operand, and whether the third operand can be converted to match the second operand. If both can be converted, or one can be converted but the conversion is ambiguous, the program is ill-formed.


Paragraphs explaining how your Foo is converted to int:

5.16/3 about condition ? E1 : E2:

Otherwise, if the second and third operand have different types, and either has (possibly cv-qualified) class type, an attempt is made to convert each of those operands to the type of the other. [...] E1 can be converted to match E2 if E1 can be implicitly converted to the type that expression E2 would have if E2 were converted to an rvalue (or the type it has, if E2 is an rvalue).

4.3 about "implicitly converted":

An expression e can be implicitly converted to a type T if and only if the declaration T t = e; is well-formed, for some invented temporary variable t.

8.5/14 about copy initialization ( T t = e; )

If the source type is a (possibly cv-qualified) class type, conversion functions are considered. The applicable conversion functions are enumerated (13.3.1.5), and the best one is chosen through overload resolution (13.3). The user-defined conversion so selected is called to convert the initializer expression into the object being initialized. If the conversion cannot be done or is ambiguous, the initialization is ill-formed.

13.3.1.5 about the conversion function candidates

The conversion functions of S and its base classes are considered. Those that are not hidden within S and yield type T or a type that can be converted to type T via a standard conversion sequence (13.3.3.1.1) are candidate functions.

戒ㄋ 2024-08-13 00:21:56

标准第 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 作为表达式类型,并且至少有一个是右值。

我可以继续第 5 段和第 6 段,但我认为很明显该表达式的类型为 int

我认为要点是:

  1. 您的编译器正在按照标准运行。

  2. 条件表达式类型的规则过于复杂,不容易掌握。不要挑战极限,因为有时你会犯错误。 (此外,这正是编译器可能无法精确实现标准的地方。)

  3. 尝试指定类型,以便第二个和第三个表达式具有相同的类型。无论如何,请尽量避免使用不属于所需类型的表达式。

This is covered in positively confusing detail in section 5.16 of the standard. The important part is in paragraph 3. "If E2 is an lvalue: E1 can be converted to match E2 if E1 can be implicitly converted (clause 4) to the type 'reference to T2', subject to the constraint that in the conversion the reference must bind directly (8.5.3) to E1."

In the expression, the only lvalue is item, so the question is whether 0 (an int) can be implicitly converted to type Foo. In this case, there is no implicit conversion of any other type to a Foo, since the only available conversion function is marked explicit. Therefore, that doesn't work, and we follow with "if E2 is an rvalue, or if the conversion above cannot be done:" (skipping the part about if they both have class type) "Otherwise (i.e., if E1 or E2 has a nonclass type, or if they both have class types but the underlying classes are not either the same or one a base class of the other): E1 can be converted to match E1 if E1 can be implicitly converted to the type that expression E2 would have if E2 were converted to an rvalue (or the type it has, if E2 is an rvalue)."

Therefore, we see that 0 is an rvalue, of type int. We can convert a Foo, since we can implicitly convert a Foo to a double and thence to an int. Then:

"Using this process, it is determined whether the second operand can be converted to match the third operand, and whether the third operand can be converted to match the second operand. If both can be converted, oor one can be converted but the conversion is ambiguous, the program is ill-formed. If neither can be converted, the operands are left unchanged and further checking is performed as described below. If exactly one conversion is possible, that conversion is applied to the chosen operand and the converted operand is used in the place of the original operand for the remainder of this section."

Since we can convert a Foo to an int, we convert the Foo to an int for the remainder of the determination. We've now got two ints as expression types, and at least one is an rvalue.

I can go on with paragraph 5 and 6, but I think it's pretty obvious that the expression has type int.

I think the takeaways are:

  1. Your compiler is functioning according to the standard.

  2. The rules on the type of a conditional expression are too complicated to be easily learned. Don't push the envelope, because you'll make a mistake sometime. (Besides, this is exactly the sort of place where a compiler might fail to implement the standard precisely.)

  3. Try to specify types so that both the second and third expression are of the same type. In any case, try to avoid expressions that are not of the desired type.

琉璃梦幻 2024-08-13 00:21:56

三元表达式的类型在编译时确定;运行时 some_condition 是什么并不重要。

我想问题是:为什么编译器在第一个示例中选择 int 而不是 double ?

The type of the ternary expression is determined at compile-time; it doesn't matter what some_condition is at runtime.

I guess the question is then: why does the compiler choose int instead of double in the first example?

原谅我要高飞 2024-08-13 00:21:56

三元运算符根据其参数猜测类型。它不能将 item 转换为 int,但可以将 item 转换为 double,然后再将其转换为 int。

ternary operator guesses the type from its arguments. it cannot convert item to int but it can convert item to double which it then converts to int.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文