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

时间:2014-08-11 10:52:02

标签: c++ language-lawyer

考虑以下简短的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转换被认为更多&#34;昂贵&#34;由编译器?

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

4 个答案:

答案 0 :(得分:51)

标准规定:

  

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

     

§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)if if:

  

对于某些发明的临时变量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是更好的匹配(因为您不必添加常量)。因此,应选择operator int

注意(可能违反直觉)标准不会考虑返回类型(即这些运算符转换的类型)一旦被添加到重载集(如上所述),前提是参数的转换顺序其中一个的优于另一个的参数的转换序列(由于常量,在这种情况下是这种情况)。

标准规定:

  

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

     
      
  • 对于某些参数j,ICSj(F1)是比ICSj(F2)更好的转换序列,或者,如果不是,
  •   
  • 上下文是通过用户定义的转换初始化,从返回类型F1到目标类型的标准转换序列(即,正在初始化的实体的类型)是比标准转换序列更好的转换序列。 F2的返回类型到目标类型。
  •   
     

§13.3.3[over.match.best]

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

答案 1 :(得分:9)

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

简短的推理是,在将b转换为bool时,重载决策的候选函数(具有隐式对象参数)

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

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

如果两个函数共享相同的资格(const或不同),则选择operator bool,因为它提供了直接转换。

通过演员表示法进行转换,逐步分析

如果我们同意调用布尔ostream插入器(std :: basic_ostream :: operator&lt;&lt;(bool val)as [ostream.inserters.arithmetic]),其值为{{1我可以深入了解转化。

1。演员表达

b对bool的演员

b

评估为

bool

根据 C ++ 11,5.4 / 4 [expr.cast] ,因为(bool)b 不适用(不在此处添加或删除const)。

如果发明变量t的static_cast<bool>(b) 格式正确,则每个 C ++ 11,5.2.9 / 4 [expr.static.cast] 允许此静态转换。 根据 C ++ 11,8.5 / 15 [dcl.init] ,这些语句称为直接初始化。

2。直接初始化const_cast

最少提及的标准段落陈述 16 (强调我的):

  

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

     

[...]

     

[...]如果源类型是(可能是cv认证的)类类型,则会考虑转换函数

     

枚举适用的转换函数,并通过重载解析选择最佳函数。

2.1哪些转换功能可用?

可用的转换函数为bool t(b);bool t(b);,因为 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 int ()适用,因为它不会隐藏在operator bool() const中并产生operator bool () const

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

  

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

这意味着int也适用。

2.3选择了哪种转换功能?

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

  

重载分辨率用于选择要调用的转换函数。

有一个特殊的&#34; quirk&#34;当涉及类成员函数的重载解析时:隐式对象参数&#34;。

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

  

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

其中非静态成员函数的此参数的类型 - 根据第4节:

  
      
  • 对于没有引用限定符或使用&amp;而声明的函数的“对cv X的左值引用” REF-限定符

  •   对于用&amp;&amp ;;声明的函数,
  • “对cv X的rvalue引用” REF-限定符

  •   
     

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

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

  

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

重载解析的候选函数是:

bool

显然,如果使用operator int ()类型的非常量对象请求转换,operator bool (B2 const &); operator int (B2 &); 是更好的匹配,因为operator int ()需要进行资格转换。

如果两个转换函数共享相同的const限定,那么这些函数的重载分辨率将不再起作用。 在这种情况下,转换(序列)排名就会到位。

3。当两个转换函数共享相同的const限定条件时,为什么选择B2

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

  

用户定义的转换序列包括初始标准转换序列,然后是用户定义的转换,后跟第二个标准转换序列。

     

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

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

  

[...]定义隐式转换序列的部分排序,基于关系更好的转换序列和更好的转换。

     

[...]用户定义的转换序列U1是比另一个用户定义的转换序列U2更好的转换序列,如果它们包含相同的用户定义的转换函数或构造函数或聚合初始化以及U1的第二个标准转换序列优于U2的第二个标准转换序列。

第二个标准转换是B2booloperator bool()(身份转换),而bool的第二个标准转换为booloperator int ()这是一个布尔转换。

因此,如果两个转换函数共享相同的const限定条件,则使用int的转换序列会更好。

答案 2 :(得分:1)

C ++ bool类型有两个值 - true和false,对应的值为1和0.如果在B2类中添加bool运算符,可以明确地调用基类(B)的bool运算符,则可以避免固有的混淆输出结果为假。 这是我修改过的程序。那么操作符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运算符,问题就会得到解决。

答案 3 :(得分:0)

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

我的贡献是,&#34;演员&#34;编译类似于&#34;重载操作&#34;,我建议为每个操作创建一个具有唯一标识符的函数,然后用必需的运算符或强制转换替换它。

#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美分。