不同的编译器调用不同的演员操作符

考虑下面这个简短的C ++程序:

#include <iostream> class B { public: operator bool() const { return false; } }; class B2 : public B { public: operator int() { return 5; } }; int main() { B2 b; std::cout << std::boolalpha << (bool)b << std::endl; } 

如果我在不同的编译器上编译它,我会得到不同的结果。 使用Clang 3.4和GCC 4.4.7它打印true ,而Visual Studio 2013打印false ,这意味着他们在(bool)b调用不同的演员操作。 根据标准哪个是正确的行为?

在我的理解operator bool()不需要转换,而operator int()需要一个intbool转换,所以编译器应该select第一个。 const是否做了这样的事情,const转换被编译器认为更“昂贵”吗?

如果我删除了const ,那么所有编译器同样产生false作为输出。 另一方面,如果我将两个类组合在一起(两个运算符将在同一个类中),那么所有三个编译器将产生true输出。

标准规定:

除非两个函数转换为相同types,否则派生类中的转换函数不会隐藏基类中的转换函数。

§12.3[class.conv]

这意味着operator bool不被operator int隐藏。

标准规定:

在重载parsing期间,隐含的对象参数与​​其他参数无法区分。

§13.3.3.1[over.match.funcs]

在这种情况下的“隐含的对象论证”是b ,它是B2 &的types。 operator bool需要const B2 & ,因此编译器必须将const添加到b才能调用operator bool 。 这 – 所有其他的东西是相等的 – 使operator int更好地匹配。

该标准规定static_cast (在这种情况下,C风格转换正在执行)可以转换为typesT (在本例中为int ),如果:

声明T t(e); 对于一些发明的临时variablest是forms良好的。

§5.2.9[expr.static.cast]

因此int可以转换为bool ,而bool可以转换为bool

标准规定:

考虑S及其基类的转换函数。 那些不隐藏在S非显式转换函数和产出typesT 或者可以通过标准转换序列转换为typesTtypes是候选函数。

§13.3.1.5[over.match.conv]

所以重载集合由operator intoperator bool 。 所有其他的东西是相等的, operator int是一个更好的匹配(因为你不必添加const)。 因此应该selectoperator int

注意(也许与直觉相反),一旦标准被添加到过载集合(如上所述),标准就不考虑返回types(即这些操作符所转换的types),只要其中一个参数的转换顺序它们优于另一个参数的转换序列(由于常数,在这种情况下是这种情况)。

标准规定:

给定这些定义,如果对于所有参数i,ICSi(F1)不是比ICSi(F2)更差的转换序列,则可行函数F1被定义为比另一个可行函数F2更好的函数,然后

  • 对于一些论元j,ICSj(F1)是比ICSj(F2)更好的转换序列,否则,
  • 上下文是由用户定义的转换进行的初始化,并且从返回typesF1到目标types(即被初始化的实体的types)的标准转换序列是比来自返回types的标准转换序列更好的转换序列F2的目标types。

§13.3.3[over.match.best]

在这种情况下,只有一个参数(隐含的this参数)。 B2 & => B2 &的转换序列(要调用operator int )优于B2 & => const B2 & (调用operator bool ),因此operator int是从重载集中select的,而不考虑它的事实实际上不直接转换成bool

由于b不是const限定的,因此转换函数operator int()是通过在operator bool() const进行clangselect的,而bool的转换运算符是。

简单的推理是当将b转换为bool时,候选函数用于重载分辨率(具有隐式对象参数)

 operator bool (B2 const &); operator int (B2 &); 

第二个是更好的匹配,因为b不是const限定的。

如果两个函数共享相同的资格(不论是否是const ),则selectoperator bool ,因为它提供了直接转换。

通过铸造符号转换,逐步分析

如果我们同意根据[ostream.inserters.arithmetic]中的boolean ostream插入器(std :: basic_ostream :: operator <<(bool val))调用带有从bbool的转换结果的值,我们可以挖掘该转换。

1.演员expression

b对bool的铸造

 (bool)b 

评估

 static_cast<bool>(b) 

按照C ++ 11,5.4 / 4 [expr.cast],因为const_cast不适用(不是在这里添加或删除const)。

如果使用bool t(b); ,则每个C ++ 11,5.2.9 / 4 [expr.static.cast]允许静态转换bool t(b); 对于一个发明的variablest是很好的形成。 这样的语句按照C ++ 11,8.5 / 15 [dcl.init]被称为直接初始化。

2.直接初始化bool t(b);

最less提到的标准段落的第16条(重点是我的):

初始化器的语义如下。 目标types是被初始化的对象或引用的types,源types是初始化expression式的types。

[…]

如果源types是(可能是cv-qualified) 类types,则考虑转换函数

枚举适用的转换函数,最好通过重载parsing来select。

2.1哪些转换function可用?

可用的转换函数是operator int ()operator bool() const因为C ++ 11,12.3 / 5 [class.conv]告诉我们:

除非两个函数转换为相同types,否则派生类中的转换函数不会隐藏基类中的转换函数。

C ++ 11,13.3.1.5/1 [over.match.conv]指出:

考虑S及其基类的转换函数。

其中S是将被转换的类。

2.2哪些转换function适用?

C ++ 11,13.3.1.5/1 [over.match.conv] (强调我的):

1 […]假设“cv1 T”是被初始化的对象的types,“cv S”是初始化expression式的types,S是类types,候选函数被select如下:考虑S及其基类的函数。 那些不隐藏在S中的非显式转换函数和产出typesT 或者可以通过标准转换序列转换为typesT的types是候选函数。

因此, operator bool () const是可用的,因为它并不隐藏在B2并产生一个bool

在最后一个标准报价中强调的部分与使用operator int ()进行转换相关,因为int是可以通过标准转换序列转换为bool的types。 从intbool的转换甚至不是一个序列,而是每个C ++ 11允许的直接转换,4.12 / 1 [conv.bool]

算术,非范型枚举,指针或指向成员types的指针可以转换为booltypes的prvalue。 零值,空指针值或空成员指针值被转换为false; 任何其他值都转换为true。

这意味着operator int ()也适用。

2.3select了哪种转换function?

通过重载分辨率( C ++ 11,13.3.1.5/1 [over.match.conv] )来select适当的转换函数:

重载parsing用于select要调用的转换函数。

当涉及类成员函数的重载parsing时,有一个特殊的“怪癖”:隐式对象参数“。

Per C ++ 11,13.3.1 [over.match.funcs]

静态和非静态成员函数都有一个隐含的对象参数[…]

其中非静态成员函数的这个参数的types – 根据子句4-是:

  • 对于不带ref-qualifier或带有&ref-qualifier声明的函数,“左值引用cv X”

  • 用&&​​ ref-qualifier声明的函数的“右值引用cv X”

其中X是函数所属的类, cv是成员函数声明中的cv限定。

这意味着(按照C ++ 11,13.3.1.5 /2 [over.match.conv] ),在一个初始化转换函数中,

他的参数列表有一个参数,它是初始化expression式。 [注:该参数将与转换函数的隐式对象参数进行比较。 – 注意]

重载parsing的候选函数是:

 operator bool (B2 const &); operator int (B2 &); 

显然,如果使用B2types的非常量对象来请求转换, operator int ()会更好,因为operator bool ()需要进行限定转换。

如果两个转换函数都具有相同的const限定条件,那么这些函数的重载parsing就不会再起作用了。 在这种情况下,转换(顺序)排名就到位了。

3.当两个转换函数共享相同的const限定时,为什么selectoperator bool ()

B2bool的转换是用户定义的转换序列( C ++ 11,13.3.3.1.2 / 1 [over.ics.user]

用户定义的转换序列由一个初始的标准转换序列,后跟一个用户定义的转换,后跟一个第二个标准转换序列组成。

[…]如果用户定义的转换由转换函数指定,则初始标准转换序列将源types转换为转换函数的隐式对象参数。

C ++ 11,13.3.3.2/3 [over.ics.rank]

[…]根据关系更好的转换序列和更好的转换来定义隐式转换序列的偏序。

如果用户定义的转换序列U1包含相同的用户定义的转换函数或构造函数或聚合初始化,并且U1的第二标准转换序列优于U2的第二个标准转换序列。

第二个标准转换是operator bool()boolbool (标识转换)的情况,而operator int ()情况下的第二个标准转换是int到布尔转换的bool

因此,如果两个转换函数共享相同的const限定,则使用operator bool ()的转换序列更好。

C ++ booltypes有两个值 – true和false,对应的值为1和0.如果在B2类中添加一个调用基类(B)的bool操作符的bool操作符,可以避免内在的混淆,然后输出作为错误。 这是我的修改程序。 然后运算符布尔意味着运算符布尔,而不是以任何方式运算符int。

 #include <iostream> class B { public: operator bool() const { return false; } }; class B2 : public B { public: operator int() { return 5; } operator bool() { return B::operator bool(); } }; int main() { B2 b; std::cout << std::boolalpha << (bool)b << std::endl; } 

在你的例子中,(bool)b试图为B2调用bool操作符,B2inheritance了bool操作符,int操作符通过支配规则,int操作符被调用,B2中inheritance了bool操作符。 但是,通过在B2类中明确拥有一个bool操作符,问题就解决了。

以前的一些答案,已经提供了很多信息。

我的贡献是“cast操作”被编译成类似的,对于“重载操作”,我build议用每个操作的唯一标识符来做一个函数,然后用所需的操作符或typesreplace它。

 #include <iostream> class B { public: bool ToBool() const { return false; } }; class B2 : public B { public: int ToInt() { return 5; } }; int main() { B2 b; std::cout << std::boolalpha << b.ToBool() << std::endl; } 

然后,应用操作员或演员。

 #include <iostream> class B { public: operator bool() { return false; } }; class B2 : public B { public: operator int() { return 5; } }; int main() { B2 b; std::cout << std::boolalpha << (bool)b << std::endl; } 

只是我2美分。