用三元运算符进行奇怪的隐式转换 [英] Strange implicit conversions with the ternary operator

查看:257
本文介绍了用三元运算符进行奇怪的隐式转换的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下代码:

class A {
public:
    operator int() const { return 5; }
};

class B {
public:
    operator int() const { return 6; }
};

int main() {
    A a;
    B b;
    int myInt = true ? a : b;
    return 0;
}

尝试使用Visual Studio 2017 RC编译该代码会导致以下错误:

错误 C2446 :::无法从B转换为A

注意:没有可用的用户定义转换运算符可以执行此转换,否则无法调用该运算符

...这是令人惊讶的,因为我希望它将它们都转换为通用类型,在这种情况下为int.

clang(4.0)成功编译了相同代码,没有任何错误或警告.

在这种情况下,哪两个是正确的,为什么?

解决方案

TL; DR; clang是正确的,因为在AB之间没有可能的转换,因此重载分辨率用于确定要应用于操作数的转换,并选择以下(虚拟)重载运算符:

int operator?:(bool, int, int);

对于任何一对算术类型,都存在?:运算符的此类(再次虚拟)重载(请参见下面的参考资料).


标准规则:

由于不能将A转换为BB转换为A,因此适用以下条件:

[expr.cond]

否则,结果为prvalue. 如果第二个和第三个操作数不具有相同的类型,并且都具有(可能是cv限定的)类类型,则将使用重载分辨率来确定要应用于这些操作数的转换(如果有)( [over.match.oper]

如果任何一个操作数的类型是类或枚举,则用户定义的运算符函数可能是 声明实现此运算符或用户定义的转换对于转换操作数可能是必需的 设置为适合内置运算符的类型.

[...]

用于重载解决方案的候选函数集是成员候选者,非成员候选者和内置候选者的并集.

如果通过重载分辨率选择了内置候选,则类型类型的操作数将转换为所选操作函数的相应参数的类型,但 [expr.compound ] .

在您的情况下,有一个内置的候选人:

[over.built#27]

对于每对提升的算术类型LR,都存在以下形式的候选算子函数

LR      operator?:(bool, L, R);

其中LR是常规算术转换的结果( [expr. arith.conv] )在类型LR之间. [注意:与候选函数的所有这些描述一样,此声明仅用于描述内置运算符,用于重载解析.运算符"?:"不能重载. — 尾注]


其他详细信息:

由于不能重载?:运算符,因此这意味着您的代码仅在两种类型都可以转换为算术类型(例如int)时才有效.作为计数器"示例,以下代码格式错误:

struct C { };
struct A { operator C() const; };
struct B { operator C() const; };

auto c = true ? A{} : B{}; // error: operands to ?: have different types 'A' and 'B'

还请注意,如果其中一种类型可转换为两种不同的算术类型,例如intfloat:

struct A { operator int() const; };
struct B { 
    operator int() const; 
    operator float() const;
};

auto c = true ? A{} : B{};

错误(来自gcc)实际上充满了信息:

错误:三元运算符不匹配:(操作数类型为'bool','A'和'B')

auto c = true ? A{} : B{};
~~~~~^~~~~~~~~~~

  • 注意:候选人:operator?:(bool, float, int) <built-in>
  • 注意:候选人:operator?:(bool, float, float) <built-in>

I have the following code:

class A {
public:
    operator int() const { return 5; }
};

class B {
public:
    operator int() const { return 6; }
};

int main() {
    A a;
    B b;
    int myInt = true ? a : b;
    return 0;
}

Attempting to compile that code with Visual Studio 2017 RC results in the following error:

error C2446: :: no conversion from B to A

note: No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called

...which is surprising because I would expect it to convert both of them to a common type, in this case int.

clang (4.0) compiles the same code successfully without any errors or warnings.

Which of the two is correct in this case, and why?

解决方案

TL;DR; clang is right, since there are no possible conversions between A and B, overload resolution is used to determine the conversions to be applied to the operands, and the following (fictive) overloaded operator is selected:

int operator?:(bool, int, int);

There exists such (again, fictive) overload of the ?: operator for any pair of arithmetic types (see references below).


Standard rules:

Since you cannot convert A to B or B to A, then the following applies:

[expr.cond]

Otherwise, the result is a prvalue. If the second and third operands do not have the same type, and either has (possibly cv-qualified) class type, overload resolution is used to determine the conversions (if any) to be applied to the operands ([over.match.oper], [over.built]). If the overload resolution fails, the program is ill-formed. Otherwise, the conversions thus determined are applied, and the converted operands are used in place of the original operands for the remainder of this subclause.

This falls back to this:

[over.match.oper]

If either operand has a type that is a class or an enumeration, a user-defined operator function might be declared that implements this operator or a user-defined conversion can be necessary to convert the operand to a type that is appropriate for a built-in operator.

[...]

The set of candidate functions for overload resolution is the union of the member candidates, the non-member candidates, and the built-in candidates.

If a built-in candidate is selected by overload resolution, the operands of class type are converted to the types of the corresponding parameters of the selected operation function, except that the second standard conversion sequence of a user-defined conversion sequence is not applied. Then the operator is treated as the corresponding built-in operator and interpreted according to [expr.compound].

In your case, there is a built-in candidate:

[over.built#27]

For every pair of promoted arithmetic types L and R, there exist candidate operator functions of the form

LR      operator?:(bool, L, R);

where LR is the result of the usual arithmetic conversions ([expr.arith.conv]) between types L and R. [ Note: As with all these descriptions of candidate functions, this declaration serves only to describe the built-in operator for purposes of overload resolution. The operator "?:" cannot be overloaded. — end note ]


Extra details:

Since the ?: operator cannot be overloaded, this means that your code only works if both types can be converted to an arithmetic type (e.g., int). As a "counter"-example, the following code is ill-formed:

struct C { };
struct A { operator C() const; };
struct B { operator C() const; };

auto c = true ? A{} : B{}; // error: operands to ?: have different types 'A' and 'B'

Also note that you would get an ambiguous "call" if one of the type is convertible to two different arithmetic types, e.g., int and float:

struct A { operator int() const; };
struct B { 
    operator int() const; 
    operator float() const;
};

auto c = true ? A{} : B{};

The error (from gcc) is actually full of information:

error: no match for ternary 'operator?:' (operand types are 'bool', 'A', and 'B')

auto c = true ? A{} : B{};
~~~~~^~~~~~~~~~~

  • note: candidate: operator?:(bool, float, int) <built-in>
  • note: candidate: operator?:(bool, float, float) <built-in>

这篇关于用三元运算符进行奇怪的隐式转换的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆