不同的编译器调用不同的转换操作符

考虑以下简短的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转换,所以编译器应该选择第一个。 const是否会对此做些什么,编译器认为const-conversion更“昂贵”?

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


该标准规定:

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

§12.3[class.conv]

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

该标准规定:

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

§13.3.3.1[over.match.funcs]

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

该标准规定static_cast (C风格转换在此实例中执行)可以转换为T类型(本例中为int ),如果:

声明T t(e); 对于一些发明的临时变量t是完美的。

§5.2.9[expr.static.cast]

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

该标准规定:

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

§13.3.1.5[over.match.conv]

所以过载集由operator intoperator bool 。 所有其他的东西都是平等的, operator int是一个更好的匹配(因为你不需要添加const)。 因此应该选择operator int

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

该标准规定:

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

  • 对于一些参数j,ICSj(F1)比ICSj(F2)的转换序列更好,或者,如果不是这样,
  • 上下文是由用户定义的转换进行的初始化,并且从返回类型F1到目标类型(即正在初始化的实体的类型)的标准转换序列是比返回类型的标准转换序列更好的转换序列F2的目标类型。
  • §13.3.3[over.match.best]

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


    转换函数operator int()operator bool() const的clang选择,因为b不是const限定的,而bool的转换运算符是。

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

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

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

    如果两个函数共享相同的限定条件(无论是否为const ),则选择operator bool ,因为它提供了直接转换。

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

    如果我们同意通过将b转换为bool而得到的值调用boost ostream插入器(std :: basic_ostream :: operator <<(bool val),按照[ostream.inserters.arithmetic]),我们可以挖掘该转换。

    1.演员表达

    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); 对于一个发明的变量t是很好形成的。 这些语句按照C ++ 11,8.5 / 15 [dcl.init]被称为直接初始化。

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

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

    初始化器的语义如下。 目标类型是被初始化的对象或引用的类型,源类型是初始化表达式的类型。

    [...]

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

    列举适用的转换函数,并通过重载分辨率选择最佳转换函数。

    2.1哪些转换功能可用?

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

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

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

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

    其中S是将从中转换而来的类。

    2.2哪些转换功能适用?

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

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

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

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

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

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

    2.3选择了哪种转换功能?

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

    重载解析用于选择要调用的转换函数。

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

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

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

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

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

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

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

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

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

    重载解析的候选函数是:

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

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

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

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

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

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

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

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

    [...]根据关系更好的转换序列和更好的转换来定义隐式转换序列的部分排序。

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

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

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


    C ++ bool类型有两个值 - 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操作符,B2继承了bool操作符,int操作符通过支配规则,int操作符被调用,B2中继承了bool操作符。 但是,通过在B2类中明确拥有一个bool操作符,问题就解决了。

    链接地址: http://www.djcxy.com/p/61643.html

    上一篇: Different cast operator called by different compilers

    下一篇: C++ Cascading type conversion